@thirdfy/agent-cli 0.1.16 → 0.1.17

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/README.md CHANGED
@@ -138,26 +138,30 @@ thirdfy-agent bootstrap complete --agent-key "0xYOUR_AGENT_KEY" --challenge-id "
138
138
 
139
139
  ## Run modes and profiles
140
140
 
141
- `thirdfy-agent` supports one command surface with three run modes:
141
+ `thirdfy-agent` supports one command surface with four run modes:
142
142
 
143
143
  - `--run-mode thirdfy` -> Thirdfy-governed execute-intent rail (default for `profile=network`)
144
144
  - `--run-mode self` -> self-custody rail (`build-tx` flow; default for `profile=personal`)
145
145
  - `--run-mode hybrid` -> self-custody + governance mirror metadata (default for `profile=builder`)
146
+ - `--run-mode agent_wallet` -> managed wallet execution rail for Claude-friendly hosted execution
146
147
 
147
148
  | Goal | Recommended mode | Why |
148
149
  | --- | --- | --- |
149
150
  | BYOW signing and local custody | `self` | Keeps signing and broadcast on your wallet path. |
150
151
  | Managed delegated execution | `thirdfy` | Uses delegated governance rail for hosted execution. |
151
152
  | Self execution plus governance mirror checks | `hybrid` | Preserves local execution while emitting managed-lane mirrors. |
153
+ | Managed wallet primary + governance mirror checks | `hybrid --hybrid-wallet-mode agent_wallet` | Uses managed wallet as primary hybrid path while still running Thirdfy mirror checks. |
152
154
 
153
155
  Auth expectations by mode:
154
156
 
155
157
  - `self`: requires execution identity (`agentApiKey`) today; keyless self lane is blocked with deterministic `SELF_OPEN_DISABLED`.
156
- - `hybrid`: requires execution identity and governed mirror semantics.
158
+ - `hybrid`: requires execution identity and governed mirror semantics (`--hybrid-wallet-mode self|agent_wallet`, default `self`).
157
159
  - `thirdfy`: requires execution identity and delegated governance path.
160
+ - `agent_wallet`: requires execution identity and delegated managed-wallet path.
158
161
  - custody mode defaults are profile-aware:
159
162
  - `managed` default for `thirdfy`
160
163
  - `external` default for `self` and `hybrid`
164
+ - `managed` default for `agent_wallet`
161
165
 
162
166
  Selection precedence:
163
167
 
@@ -192,6 +196,19 @@ thirdfy-agent run \
192
196
 
193
197
  The default `run --run-mode self` behavior is still unsigned-only (advanced users can sign externally).
194
198
 
199
+ Hybrid managed-wallet variant (agent_wallet + Thirdfy mirror):
200
+
201
+ ```bash
202
+ thirdfy-agent run \
203
+ --agent-api-key "$AGENT_API_KEY" \
204
+ --run-mode hybrid \
205
+ --hybrid-wallet-mode agent_wallet \
206
+ --action swap \
207
+ --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000","chainId":8453}' \
208
+ --estimated-amount-usd 25 \
209
+ --json
210
+ ```
211
+
195
212
  Self lane effect-check controls:
196
213
 
197
214
  ```bash
@@ -273,7 +290,7 @@ Use `create` + `activate` as the default delegated execution lifecycle:
273
290
 
274
291
  ```bash
275
292
  thirdfy-agent delegation create \
276
- --auth-token "$THIRDFY_AUTH_TOKEN" \
293
+ --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN" \
277
294
  --agent-key "0xYOUR_AGENT_KEY" \
278
295
  --owner-address "0xOWNER_WALLET" \
279
296
  --token-address "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" \
@@ -282,7 +299,7 @@ thirdfy-agent delegation create \
282
299
 
283
300
  # Sign the returned `data.delegation` payload in your wallet, then:
284
301
  thirdfy-agent delegation activate \
285
- --auth-token "$THIRDFY_AUTH_TOKEN" \
302
+ --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN" \
286
303
  --agent-key "0xYOUR_AGENT_KEY" \
287
304
  --wallet-address "0xOWNER_WALLET" \
288
305
  --session-account-address "0xSESSION_ACCOUNT" \
@@ -292,25 +309,25 @@ thirdfy-agent delegation activate \
292
309
  --json
293
310
 
294
311
  thirdfy-agent delegation status \
295
- --auth-token "$THIRDFY_AUTH_TOKEN" \
312
+ --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN" \
296
313
  --agent-key "0xYOUR_AGENT_KEY" \
297
314
  --verify \
298
315
  --json
299
316
 
300
317
  thirdfy-agent delegation inspect \
301
- --auth-token "$THIRDFY_AUTH_TOKEN" \
318
+ --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN" \
302
319
  --agent-key "0xYOUR_AGENT_KEY" \
303
320
  --verify \
304
321
  --json
305
322
 
306
323
  thirdfy-agent delegation revoke \
307
- --auth-token "$THIRDFY_AUTH_TOKEN" \
324
+ --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN" \
308
325
  --agent-key "0xYOUR_AGENT_KEY" \
309
326
  --reason "user_revoked" \
310
327
  --json
311
328
 
312
329
  thirdfy-agent delegation balance \
313
- --auth-token "$THIRDFY_AUTH_TOKEN" \
330
+ --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN" \
314
331
  --agent-key "0xYOUR_AGENT_KEY" \
315
332
  --json
316
333
 
@@ -323,6 +340,8 @@ thirdfy-agent delegation redeem \
323
340
  --json
324
341
  ```
325
342
 
343
+ `--auth-token "$THIRDFY_AUTH_TOKEN"` remains supported for all delegation commands, but owner-session token is preferred for least-privilege owner operations.
344
+
326
345
  ### MetaMask custodial-compatible lifecycle (new)
327
346
 
328
347
  ```bash
@@ -23,7 +23,8 @@ const BITFINEX_REQUIRED_SCOPE_PERMISSIONS = {
23
23
  funding: { read: 1, write: 1 },
24
24
  wallets: { read: 1, write: 1 },
25
25
  };
26
- const RUN_MODES = ['thirdfy', 'self', 'hybrid'];
26
+ const RUN_MODES = ['thirdfy', 'self', 'hybrid', 'agent_wallet'];
27
+ const HYBRID_WALLET_MODES = ['self', 'agent_wallet'];
27
28
  const CUSTODY_MODES = ['managed', 'external'];
28
29
  const EFFECT_CHECK_MODES = ['strict', 'relaxed', 'off'];
29
30
  const PROFILE_DEFAULTS = {
@@ -157,6 +158,9 @@ async function main() {
157
158
  case 'managed run':
158
159
  await commandManagedRun(context, subFlags, capabilities);
159
160
  return;
161
+ case 'managed setup':
162
+ await commandManagedSetup(context, subFlags, capabilities);
163
+ return;
160
164
  case 'jeff trade':
161
165
  await commandRun(context, subFlags, capabilities);
162
166
  return;
@@ -549,8 +553,9 @@ async function commandPreflight(ctx, flags, capabilities) {
549
553
  const resolved = await resolveActionSelection(ctx, flags);
550
554
  prepareActionParamsForFlags(flags, resolved.resolvedAction);
551
555
  const runMode = getEffectiveRunMode(ctx, flags);
556
+ const hybridWalletMode = runMode === 'hybrid' ? normalizeHybridWalletMode(flags.hybridWalletMode) : null;
552
557
  enforceChainCompatibility(capabilities, flags, runMode);
553
- const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved);
558
+ const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved, { hybridWalletMode });
554
559
  const normalized = applyExecutionFallbackHints(backendResult.normalized, {
555
560
  flags,
556
561
  runMode,
@@ -569,6 +574,7 @@ async function commandPreflight(ctx, flags, capabilities) {
569
574
  resolvedAction: resolved.resolvedAction,
570
575
  preflightRoute: backendResult.route,
571
576
  runMode,
577
+ hybridWalletMode,
572
578
  profile: ctx.profile || null,
573
579
  swapInput: flags.__swapInput || null,
574
580
  capabilitiesVersion: capabilities?.contractVersion || null,
@@ -581,10 +587,12 @@ async function commandRun(ctx, flags, capabilities) {
581
587
  const resolved = await resolveActionSelection(ctx, flags);
582
588
  prepareActionParamsForFlags(flags, resolved.resolvedAction);
583
589
  const runMode = getEffectiveRunMode(ctx, flags);
590
+ const hybridWalletMode = runMode === 'hybrid' ? normalizeHybridWalletMode(flags.hybridWalletMode) : null;
584
591
  enforceChainCompatibility(capabilities, flags, runMode);
585
592
  const skipPreflight = Boolean(flags.skipPreflight);
586
593
  const backendResult = await runExecutionByMode(runMode, ctx, flags, resolved, {
587
594
  skipPreflight,
595
+ hybridWalletMode,
588
596
  });
589
597
  const normalized = applyExecutionFallbackHints(backendResult.normalized, {
590
598
  flags,
@@ -622,6 +630,7 @@ async function commandRun(ctx, flags, capabilities) {
622
630
  resolvedAction: resolved.resolvedAction,
623
631
  idempotencyKey: backendResult.idempotencyKey || null,
624
632
  runMode,
633
+ hybridWalletMode,
625
634
  profile: ctx.profile || null,
626
635
  skippedPreflight: skipPreflight,
627
636
  swapInput: flags.__swapInput || null,
@@ -952,8 +961,8 @@ async function commandDelegationBalance(ctx, flags, capabilities) {
952
961
 
953
962
  async function commandDelegationRedeem(ctx, flags, capabilities) {
954
963
  const selectedRunMode = getEffectiveRunMode(ctx, flags);
955
- if (!['thirdfy', 'hybrid'].includes(selectedRunMode)) {
956
- throw createCliError('DELEGATION_REDEEM_MODE_INVALID', 'delegation redeem supports only --run-mode thirdfy|hybrid');
964
+ if (!['thirdfy', 'hybrid', 'agent_wallet'].includes(selectedRunMode)) {
965
+ throw createCliError('DELEGATION_REDEEM_MODE_INVALID', 'delegation redeem supports only --run-mode thirdfy|hybrid|agent_wallet');
957
966
  }
958
967
  const delegatedFlags = {
959
968
  ...flags,
@@ -1591,6 +1600,83 @@ async function commandManagedRun(ctx, flags, capabilities) {
1591
1600
  if (!normalized.success) process.exit(1);
1592
1601
  }
1593
1602
 
1603
+ async function commandManagedSetup(ctx, flags, capabilities) {
1604
+ const mode = normalizeManagedRunMode(flags.runMode || 'agent_wallet');
1605
+ const authHeaders = buildOwnerAuthHeaders(
1606
+ flags,
1607
+ 'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for managed setup'
1608
+ );
1609
+ const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
1610
+ const chainId = Number(flags.chainId || 8453);
1611
+ const tokenAddress = String(flags.tokenAddress || '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913').trim();
1612
+ const maxUsdPerDay = Number(flags.maxUsdPerDay || 1000);
1613
+ const initResponse = await requestWithRouteFallback(ctx, {
1614
+ method: 'POST',
1615
+ routes: ['/api/v1/agent/delegation/init-custodial'],
1616
+ body: { chainId },
1617
+ headers: { ...authHeaders },
1618
+ });
1619
+ const grantResponse = await requestWithRouteFallback(ctx, {
1620
+ method: 'POST',
1621
+ routes: ['/api/v1/agent/delegation/custodial-grant'],
1622
+ body: {
1623
+ chainId,
1624
+ agentKey,
1625
+ tokenAddress,
1626
+ maxUsdPerDay,
1627
+ periodDuration: Number(flags.periodDuration || 86_400),
1628
+ expirySeconds: Number(flags.expirySeconds || 86_400),
1629
+ },
1630
+ headers: { ...authHeaders },
1631
+ });
1632
+ let readiness = null;
1633
+ let readinessError = null;
1634
+ try {
1635
+ readiness = await apiGet(
1636
+ ctx,
1637
+ `/api/v1/agent/delegation/check?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`,
1638
+ { ...authHeaders }
1639
+ );
1640
+ } catch (error) {
1641
+ readinessError = {
1642
+ message: error?.message || 'Delegation readiness check failed',
1643
+ statusCode: Number(error?.statusCode || 0) || undefined,
1644
+ payload: error?.payload && typeof error.payload === 'object' ? error.payload : undefined,
1645
+ };
1646
+ }
1647
+ const success = initResponse?.success === true && grantResponse?.success === true;
1648
+ printEnvelope({
1649
+ success,
1650
+ code: success ? 'MANAGED_SETUP_READY' : 'MANAGED_SETUP_FAILED',
1651
+ message: success
1652
+ ? readinessError
1653
+ ? `Managed wallet setup completed, but readiness check failed. Run with --run-mode ${mode}.`
1654
+ : `Managed wallet setup completed. Run with --run-mode ${mode}.`
1655
+ : 'Managed wallet setup failed',
1656
+ data: {
1657
+ runMode: mode,
1658
+ init: initResponse,
1659
+ grant: grantResponse,
1660
+ readiness: readiness ? normalizeDelegationStatus(readiness) : null,
1661
+ readinessError,
1662
+ },
1663
+ meta: {
1664
+ apiBase: ctx.apiBase,
1665
+ lane: 'managed_easy_wallet',
1666
+ agentKey,
1667
+ chainId,
1668
+ tokenAddress,
1669
+ maxUsdPerDay,
1670
+ upgradeHint:
1671
+ mode === 'agent_wallet'
1672
+ ? 'Use --run-mode self for strict BYOW local-sign, or --run-mode agent_wallet for managed execution.'
1673
+ : null,
1674
+ capabilitiesVersion: capabilities?.contractVersion || null,
1675
+ },
1676
+ });
1677
+ if (!success) process.exit(1);
1678
+ }
1679
+
1594
1680
  async function commandProfileInit(ctx, flags) {
1595
1681
  const current = loadProfileConfig();
1596
1682
  const profile = normalizeProfileName(flags.profile || flags.name || 'personal');
@@ -1900,7 +1986,7 @@ function normalizeRunMode(value, flagName = '--run-mode') {
1900
1986
  }
1901
1987
 
1902
1988
  function defaultCustodyModeForRunMode(runMode) {
1903
- return runMode === 'thirdfy' ? 'managed' : 'external';
1989
+ return runMode === 'thirdfy' || runMode === 'agent_wallet' ? 'managed' : 'external';
1904
1990
  }
1905
1991
 
1906
1992
  function normalizeCustodyMode(value, runModeHint = 'thirdfy') {
@@ -1914,11 +2000,17 @@ function normalizeCustodyMode(value, runModeHint = 'thirdfy') {
1914
2000
  function normalizeManagedRunMode(value) {
1915
2001
  const mode = normalizeRunMode(value || 'thirdfy');
1916
2002
  if (mode === 'self') {
1917
- throw new Error('Managed lane does not support run-mode=self. Use thirdfy or hybrid.');
2003
+ throw new Error('Managed lane does not support run-mode=self. Use thirdfy, hybrid, or agent_wallet.');
1918
2004
  }
1919
2005
  return mode;
1920
2006
  }
1921
2007
 
2008
+ function normalizeHybridWalletMode(value) {
2009
+ const normalized = String(value || '').trim().toLowerCase() || 'self';
2010
+ if (HYBRID_WALLET_MODES.includes(normalized)) return normalized;
2011
+ throw new Error(`Unsupported --hybrid-wallet-mode "${value}". Supported: ${HYBRID_WALLET_MODES.join(', ')}`);
2012
+ }
2013
+
1922
2014
  function getProfileConfigPath() {
1923
2015
  return path.join(os.homedir(), '.thirdfy', 'config.json');
1924
2016
  }
@@ -2076,6 +2168,7 @@ function applyExecutionFallbackHints(normalized, { flags, runMode, resolvedActio
2076
2168
  if (
2077
2169
  looksBitfinexAction &&
2078
2170
  runMode !== 'thirdfy' &&
2171
+ runMode !== 'agent_wallet' &&
2079
2172
  (topBlockedReason === 'SELF_MODE_REQUIRES_BUILD_TX' || topBlockedReason === 'HYBRID_MODE_REQUIRES_BUILD_TX')
2080
2173
  ) {
2081
2174
  next.onboardingHint = {
@@ -2094,7 +2187,7 @@ function shouldUseBuildTxPreflight(runMode, resolved) {
2094
2187
  return meta.supportsExecuteIntent === false && meta.supportsBuildTx === true;
2095
2188
  }
2096
2189
 
2097
- async function runPreflightByMode(runMode, ctx, flags, resolved) {
2190
+ async function runPreflightByMode(runMode, ctx, flags, resolved, options = {}) {
2098
2191
  const resolvedAction = resolved.resolvedAction;
2099
2192
  if (runMode === 'self') {
2100
2193
  const route = shouldUseBuildTxPreflight(runMode, resolved) ? 'build_tx' : 'execute_intent_validation';
@@ -2115,6 +2208,44 @@ async function runPreflightByMode(runMode, ctx, flags, resolved) {
2115
2208
  normalized,
2116
2209
  };
2117
2210
  }
2211
+ if (runMode === 'hybrid' && options.hybridWalletMode === 'agent_wallet') {
2212
+ const managedPayload = buildIntentPayload(flags, {
2213
+ validationOnly: true,
2214
+ forceIdempotency: false,
2215
+ resolvedAction,
2216
+ runMode: 'agent_wallet',
2217
+ mirrorOnly: false,
2218
+ hybridWalletMode: 'agent_wallet',
2219
+ });
2220
+ const managedResponse = await apiPost(ctx, '/api/v1/agent/execute-intent', managedPayload);
2221
+ const managedNormalized = normalizeIntentResponse(managedResponse);
2222
+ const mirrorPayload = buildIntentPayload(flags, {
2223
+ validationOnly: true,
2224
+ forceIdempotency: false,
2225
+ resolvedAction,
2226
+ runMode: 'hybrid',
2227
+ mirrorOnly: true,
2228
+ hybridWalletMode: 'agent_wallet',
2229
+ });
2230
+ const mirrorResponse = await apiPost(ctx, '/api/v1/agent/execute-intent', mirrorPayload);
2231
+ const mirrorNormalized = normalizeIntentResponse(mirrorResponse);
2232
+ return {
2233
+ message:
2234
+ managedNormalized.success && mirrorNormalized.success
2235
+ ? 'Hybrid preflight completed (agent_wallet + thirdfy mirror)'
2236
+ : 'Hybrid preflight failed (agent_wallet + thirdfy mirror)',
2237
+ route: 'execute_intent_validation_dual',
2238
+ normalized: normalizeIntentResponse({
2239
+ success: Boolean(managedNormalized.success && mirrorNormalized.success),
2240
+ status: mirrorNormalized.status || managedNormalized.status || 'failed',
2241
+ mode: 'hybrid',
2242
+ hybridWalletMode: 'agent_wallet',
2243
+ managed: managedNormalized,
2244
+ mirror: mirrorNormalized,
2245
+ error: managedNormalized.error || mirrorNormalized.error || null,
2246
+ }),
2247
+ };
2248
+ }
2118
2249
  const payload = buildIntentPayload(flags, {
2119
2250
  validationOnly: true,
2120
2251
  forceIdempotency: false,
@@ -2138,8 +2269,8 @@ async function runPreflightByMode(runMode, ctx, flags, resolved) {
2138
2269
  }
2139
2270
 
2140
2271
  async function runExecutionByMode(runMode, ctx, flags, resolved, options) {
2141
- if (runMode === 'thirdfy') {
2142
- const normalized = await executeThirdfyRun(ctx, flags, resolved, options);
2272
+ if (runMode === 'thirdfy' || runMode === 'agent_wallet') {
2273
+ const normalized = await executeThirdfyRun(ctx, flags, resolved, options, runMode);
2143
2274
  const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
2144
2275
  return {
2145
2276
  code: normalized.success ? 'RUN_QUEUED' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'RUN_FAILED',
@@ -2172,10 +2303,15 @@ async function runExecutionByMode(runMode, ctx, flags, resolved, options) {
2172
2303
  }
2173
2304
  const normalized = await executeHybridRun(ctx, flags, resolved, options);
2174
2305
  const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
2306
+ const hybridWalletMode = normalizeHybridWalletMode(
2307
+ normalized.hybridWalletMode || options?.hybridWalletMode || flags.hybridWalletMode || 'self',
2308
+ );
2175
2309
  return {
2176
2310
  code: normalized.success ? 'HYBRID_READY' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'HYBRID_FAILED',
2177
2311
  message: normalized.success
2178
- ? 'Hybrid execution prepared (self tx + mirror preflight)'
2312
+ ? hybridWalletMode === 'agent_wallet'
2313
+ ? 'Hybrid execution prepared (agent wallet + mirror preflight)'
2314
+ : 'Hybrid execution prepared (self tx + mirror preflight)'
2179
2315
  : preflightBlocked
2180
2316
  ? 'Preflight blocked hybrid execution'
2181
2317
  : 'Hybrid execution failed',
@@ -2184,15 +2320,16 @@ async function runExecutionByMode(runMode, ctx, flags, resolved, options) {
2184
2320
  };
2185
2321
  }
2186
2322
 
2187
- async function executeThirdfyRun(ctx, flags, resolved, options) {
2323
+ async function executeThirdfyRun(ctx, flags, resolved, options, runMode = 'thirdfy') {
2188
2324
  const skipPreflight = Boolean(options?.skipPreflight);
2189
2325
  if (!skipPreflight) {
2190
2326
  const preflightPayload = buildIntentPayload(flags, {
2191
2327
  validationOnly: true,
2192
2328
  forceIdempotency: false,
2193
2329
  resolvedAction: resolved.resolvedAction,
2194
- runMode: 'thirdfy',
2330
+ runMode,
2195
2331
  mirrorOnly: false,
2332
+ hybridWalletMode: options?.hybridWalletMode,
2196
2333
  });
2197
2334
  const preflight = await apiPost(ctx, '/api/v1/agent/execute-intent', preflightPayload);
2198
2335
  const preflightNormalized = normalizeIntentResponse(preflight);
@@ -2208,8 +2345,9 @@ async function executeThirdfyRun(ctx, flags, resolved, options) {
2208
2345
  validationOnly: false,
2209
2346
  forceIdempotency: true,
2210
2347
  resolvedAction: resolved.resolvedAction,
2211
- runMode: 'thirdfy',
2348
+ runMode,
2212
2349
  mirrorOnly: false,
2350
+ hybridWalletMode: options?.hybridWalletMode,
2213
2351
  });
2214
2352
  const response = await apiPost(ctx, '/api/v1/agent/execute-intent', payload);
2215
2353
  const normalized = normalizeIntentResponse(response);
@@ -2261,6 +2399,36 @@ async function executeSelfRun(ctx, flags, resolved, options) {
2261
2399
  }
2262
2400
 
2263
2401
  async function executeHybridRun(ctx, flags, resolved, options) {
2402
+ const hybridWalletMode = normalizeHybridWalletMode(options?.hybridWalletMode || flags.hybridWalletMode || 'self');
2403
+ if (hybridWalletMode === 'agent_wallet') {
2404
+ const managedResult = await executeThirdfyRun(ctx, flags, resolved, options, 'agent_wallet');
2405
+ if (!managedResult.success) {
2406
+ return normalizeIntentResponse({
2407
+ ...managedResult,
2408
+ mode: 'hybrid',
2409
+ hybridWalletMode,
2410
+ });
2411
+ }
2412
+ const mirrorPayload = buildIntentPayload(flags, {
2413
+ validationOnly: true,
2414
+ forceIdempotency: true,
2415
+ resolvedAction: resolved.resolvedAction,
2416
+ runMode: 'hybrid',
2417
+ mirrorOnly: true,
2418
+ hybridWalletMode,
2419
+ });
2420
+ const mirrorResponse = await apiPost(ctx, '/api/v1/agent/execute-intent', mirrorPayload);
2421
+ const mirrorNormalized = normalizeIntentResponse(mirrorResponse);
2422
+ return normalizeIntentResponse({
2423
+ success: Boolean(managedResult.success && mirrorNormalized.success),
2424
+ status: managedResult.status || mirrorNormalized.status || 'queued',
2425
+ mode: 'hybrid',
2426
+ hybridWalletMode,
2427
+ idempotencyKey: managedResult.idempotencyKey || mirrorPayload.idempotencyKey,
2428
+ managed: managedResult,
2429
+ mirror: mirrorNormalized,
2430
+ });
2431
+ }
2264
2432
  const selfResult = await executeSelfRun(ctx, flags, resolved, {
2265
2433
  ...(options || {}),
2266
2434
  runMode: 'hybrid',
@@ -2281,6 +2449,7 @@ async function executeHybridRun(ctx, flags, resolved, options) {
2281
2449
  success: Boolean(selfResult.success && mirrorNormalized.success),
2282
2450
  status: mirrorNormalized.status || 'ready',
2283
2451
  mode: 'hybrid',
2452
+ hybridWalletMode,
2284
2453
  idempotencyKey: mirrorPayload.idempotencyKey,
2285
2454
  self: selfResult,
2286
2455
  mirror: mirrorNormalized,
@@ -2315,6 +2484,7 @@ function buildIntentPayload(flags, options) {
2315
2484
  payload.executionStrategy = {
2316
2485
  runMode: String(options.runMode),
2317
2486
  mirrorOnly: Boolean(options.mirrorOnly),
2487
+ ...(options.hybridWalletMode ? { hybridWalletMode: String(options.hybridWalletMode) } : {}),
2318
2488
  };
2319
2489
  }
2320
2490
  if (options.forceIdempotency) {
@@ -2473,17 +2643,26 @@ function getAuthToken(flags, missingMessage) {
2473
2643
 
2474
2644
  function buildOwnerAuthHeaders(flags, missingMessage) {
2475
2645
  const config = loadProfileConfig();
2476
- const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
2477
- const ownerSessionToken = String(
2478
- flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || config?.auth?.ownerSessionToken || ''
2479
- ).trim();
2480
- if (!authToken && !ownerSessionToken) {
2481
- throw new Error(missingMessage);
2646
+ const explicitOwnerSessionToken = String(flags.ownerSessionToken || '').trim();
2647
+ const explicitAuthToken = String(flags.authToken || '').trim();
2648
+ const ownerSessionToken = String(process.env.THIRDFY_OWNER_SESSION_TOKEN || config?.auth?.ownerSessionToken || '').trim();
2649
+ const authToken = String(process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
2650
+
2651
+ // Prefer explicit flags first; then prefer owner-session over bearer from env/config
2652
+ // to keep owner operations on least-privilege credentials by default.
2653
+ if (explicitOwnerSessionToken) {
2654
+ return { 'x-owner-session-token': explicitOwnerSessionToken };
2655
+ }
2656
+ if (explicitAuthToken) {
2657
+ return { Authorization: `Bearer ${explicitAuthToken}` };
2658
+ }
2659
+ if (ownerSessionToken) {
2660
+ return { 'x-owner-session-token': ownerSessionToken };
2482
2661
  }
2483
2662
  if (authToken) {
2484
2663
  return { Authorization: `Bearer ${authToken}` };
2485
2664
  }
2486
- return { 'x-owner-session-token': ownerSessionToken };
2665
+ throw new Error(missingMessage);
2487
2666
  }
2488
2667
 
2489
2668
  function extractActions(response) {
@@ -2969,8 +3148,8 @@ function printHelp() {
2969
3148
  thirdfy-agent ${CLI_VERSION}
2970
3149
 
2971
3150
  Core commands:
2972
- thirdfy-agent profile init [--profile personal|builder|network] [--run-mode thirdfy|self|hybrid] [--custody-mode managed|external] [--json]
2973
- thirdfy-agent profile use --profile <name> [--run-mode thirdfy|self|hybrid] [--custody-mode managed|external] [--json]
3151
+ thirdfy-agent profile init [--profile personal|builder|network] [--run-mode thirdfy|self|hybrid|agent_wallet] [--custody-mode managed|external] [--json]
3152
+ thirdfy-agent profile use --profile <name> [--run-mode thirdfy|self|hybrid|agent_wallet] [--custody-mode managed|external] [--json]
2974
3153
  thirdfy-agent profile show [--json]
2975
3154
  thirdfy-agent config set --key <path> --value <value> [--json]
2976
3155
  thirdfy-agent login [--auth-token <token> | --owner-session-token <token>] [--agent-api-key <key>] [--agent-key <key>] [--run-mode <mode>] [--custody-mode managed|external] [--wallet-address <addr>] [--json]
@@ -2978,19 +3157,20 @@ Core commands:
2978
3157
  thirdfy-agent whoami [--json]
2979
3158
  thirdfy-agent catalogs list [--json]
2980
3159
  thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
2981
- thirdfy-agent delegation create --auth-token <token> --agent-key <key> --owner-address <addr> --token-address <addr> --max-usd-per-day <usd> [--session-account-address <addr>] [--json]
3160
+ thirdfy-agent delegation create [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --owner-address <addr> --token-address <addr> --max-usd-per-day <usd> [--session-account-address <addr>] [--json]
2982
3161
  thirdfy-agent delegation init-custodial [--auth-token <token> | --owner-session-token <token>] [--chain-id <id>] [--json]
2983
3162
  thirdfy-agent delegation custodial-grant [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
2984
- thirdfy-agent delegation activate --auth-token <token> --agent-key <key> --wallet-address <addr> --session-account-address <addr> --delegation-manager <addr> --delegation <json> --signature <hex> [--json]
2985
- thirdfy-agent delegation status --auth-token <token> --agent-key <key> [--verify] [--json]
2986
- thirdfy-agent delegation show --auth-token <token> --agent-key <key> [--verify] [--json]
2987
- thirdfy-agent delegation inspect --auth-token <token> --agent-key <key> [--verify] [--json]
2988
- thirdfy-agent delegation balance --auth-token <token> --agent-key <key> [--chain-id <id>] [--json]
2989
- thirdfy-agent delegation revoke --auth-token <token> --agent-key <key> [--reason <text>] [--chain-id <id>] [--json]
2990
- thirdfy-agent delegation redeem --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid] [--estimated-amount-usd <usd>] [--json]
3163
+ thirdfy-agent delegation activate [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --wallet-address <addr> --session-account-address <addr> --delegation-manager <addr> --delegation <json> --signature <hex> [--json]
3164
+ thirdfy-agent delegation status [--auth-token <token> | --owner-session-token <token>] --agent-key <key> [--verify] [--json]
3165
+ thirdfy-agent delegation show [--auth-token <token> | --owner-session-token <token>] --agent-key <key> [--verify] [--json]
3166
+ thirdfy-agent delegation inspect [--auth-token <token> | --owner-session-token <token>] --agent-key <key> [--verify] [--json]
3167
+ thirdfy-agent delegation balance [--auth-token <token> | --owner-session-token <token>] --agent-key <key> [--chain-id <id>] [--json]
3168
+ thirdfy-agent delegation revoke [--auth-token <token> | --owner-session-token <token>] --agent-key <key> [--reason <text>] [--chain-id <id>] [--json]
3169
+ thirdfy-agent delegation redeem --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid|agent_wallet] [--estimated-amount-usd <usd>] [--json]
2991
3170
  thirdfy-agent managed wallet init [--auth-token <token> | --owner-session-token <token>] [--chain-id <id>] [--json]
2992
3171
  thirdfy-agent managed wallet grant [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
2993
- thirdfy-agent managed run --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid] [--idempotency-key <key>] [--json]
3172
+ thirdfy-agent managed setup [--auth-token <token> | --owner-session-token <token>] --agent-key <key> [--run-mode thirdfy|hybrid|agent_wallet] [--token-address <addr>] [--max-usd-per-day <usd>] [--chain-id <id>] [--json]
3173
+ thirdfy-agent managed run --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid|agent_wallet] [--idempotency-key <key>] [--json]
2994
3174
  thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--verify-permissions] [--skip-permissions-probe] [--bitfinex-base-url <url>] [--json]
2995
3175
  thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
2996
3176
  thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
@@ -3017,7 +3197,8 @@ Core commands:
3017
3197
  Global flags:
3018
3198
  --api-base <url> default: https://api.thirdfy.com
3019
3199
  --mcp-base <url> default: https://mcp.thirdfy.com (bootstrap token issuance)
3020
- --run-mode <mode> thirdfy | self | hybrid
3200
+ --run-mode <mode> thirdfy | self | hybrid | agent_wallet
3201
+ --hybrid-wallet-mode <mode> self | agent_wallet (only with --run-mode hybrid)
3021
3202
  --custody-mode <mode> managed | external
3022
3203
  --profile <name> personal | builder | network
3023
3204
  --env <file> load env vars from file
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "thirdfy-agent": "./bin/thirdfy-agent.mjs"
7
+ "thirdfy-agent": "bin/thirdfy-agent.mjs"
8
8
  },
9
9
  "files": [
10
10
  "bin/",
@@ -57,6 +57,8 @@ function normalizeIntentResponse(response) {
57
57
  idempotencyKey: response?.idempotencyKey || null,
58
58
  mirror: response?.mirror || null,
59
59
  self: response?.self || null,
60
+ managed: response?.managed || null,
61
+ hybridWalletMode: response?.hybridWalletMode || null,
60
62
  };
61
63
  }
62
64