@thirdfy/agent-cli 0.2.69 → 0.2.71
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 +16 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/core/actionTimeouts.mjs +3 -1
- package/src/core/executionContext.mjs +4 -0
- package/src/core/hostedRuntimeIdempotency.mjs +44 -0
- package/src/runtime/actions/selection.mjs +53 -1
- package/src/runtime/execution/earnclawRuntimeWriteGate.mjs +7 -63
- package/src/runtime/execution/hostedRuntimeWriteGate.mjs +56 -0
- package/src/runtime/execution/managedReadExecuteRail.mjs +14 -0
- package/src/runtime/execution/payloads.mjs +19 -3
- package/src/runtime/execution/runners.mjs +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.71] - 2026-09-16
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `get_avantis_setup_status` / `get_avantis_onboarding_plan` use the 180s compose timeout. Empty queued `/execute-intent` fallbacks fail as `MANAGED_READ_EMPTY_EXECUTE_INTENT` instead of a successful empty setup payload. Pin: `test/actionTimeouts.test.cjs`, `test/agent-wallet-readonly-fallback.test.cjs`. Matches `thirdfy-mcp` **0.0.121**.
|
|
12
|
+
|
|
13
|
+
## [0.2.70] - 2026-09-16
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- `--action get_aave_markets` (and other snake/kebab pairs) resolves to the first-class catalog key before colliding earn aliases. Matches `thirdfy-mcp` **0.0.120**. Pin: `test/catalog-canonical-action-resolution.test.cjs`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Hosted runtime write idempotency is generic (`hostedRuntimeIdempotency`) instead of EarnOrg-only helpers. Keys still use the `earnclaw:{runtimeId}:` wire prefix so Thirdfy action-event join stays the same. Pairs with `thirdfy-mcp` **0.0.120** and Thirdfy API **3.14.48**.
|
|
22
|
+
|
|
7
23
|
## [0.2.69] - 2026-09-16
|
|
8
24
|
|
|
9
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -40,10 +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.71
|
|
44
44
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
45
|
+
- Avantis setup-status and onboarding-plan reads wait for the compose timeout instead of falling back early.
|
|
46
|
+
- An empty queued execute-intent fallback fails instead of looking like a successful empty setup payload.
|
|
47
47
|
- See [CHANGELOG.md](./CHANGELOG.md) and GitHub Releases for older versions.
|
|
48
48
|
|
|
49
49
|
## Quick start
|
package/package.json
CHANGED
|
@@ -2,6 +2,8 @@ import { DEFAULT_TIMEOUT_MS } from './constants.mjs';
|
|
|
2
2
|
|
|
3
3
|
const LONG_RUNNING_ACTIONS = new Set([
|
|
4
4
|
'complete_avantis_onboarding',
|
|
5
|
+
'get_avantis_setup_status',
|
|
6
|
+
'get_avantis_onboarding_plan',
|
|
5
7
|
'complete_lighter_onboarding',
|
|
6
8
|
'setup_lighter_api_key',
|
|
7
9
|
'register_lighter_api_key',
|
|
@@ -23,7 +25,7 @@ function normalizeActionId(action) {
|
|
|
23
25
|
.toLowerCase();
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
/** Longer HTTP timeout for Lighter onboarding and Morpho/Aave earn writes
|
|
28
|
+
/** Longer HTTP timeout for Avantis/Lighter onboarding, Avantis setup-status compose, and Morpho/Aave earn writes. */
|
|
27
29
|
export function resolveActionTimeoutMs(action, baseTimeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
28
30
|
const base = Number.isFinite(baseTimeoutMs) && baseTimeoutMs > 0 ? baseTimeoutMs : DEFAULT_TIMEOUT_MS;
|
|
29
31
|
if (!LONG_RUNNING_ACTIONS.has(normalizeActionId(action))) return base;
|
|
@@ -50,3 +50,7 @@ export function mergeExecutionContext(body) {
|
|
|
50
50
|
if (!executionContext.source) executionContext.source = 'hosted_runtime';
|
|
51
51
|
return { ...body, executionContext };
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
wrapWriteIdempotencyKeyForHostedRuntime,
|
|
56
|
+
} from './hostedRuntimeIdempotency.mjs';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/** Stable Thirdfy execute namespace for hosted runtimes (any control plane). Legacy wire token. */
|
|
4
|
+
export const HOSTED_RUNTIME_IDEMPOTENCY_NAMESPACE = 'earnclaw';
|
|
5
|
+
|
|
6
|
+
const UUID_RE =
|
|
7
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
8
|
+
|
|
9
|
+
export const HOSTED_RUNTIME_SCOPED_KEY_RE = new RegExp(
|
|
10
|
+
`^${HOSTED_RUNTIME_IDEMPOTENCY_NAMESPACE}:[0-9a-f-]{36}:`,
|
|
11
|
+
'i',
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export function readHostedRuntimeUuid(value) {
|
|
15
|
+
const trimmed = String(value || '').trim();
|
|
16
|
+
return UUID_RE.test(trimmed) ? trimmed.toLowerCase() : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isHostedRuntimeScopedIdempotencyKey(key) {
|
|
20
|
+
return HOSTED_RUNTIME_SCOPED_KEY_RE.test(String(key || '').trim());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatHostedRuntimeScopedIdempotencyKey({ runtimeId, channel, suffix }) {
|
|
24
|
+
const rid = readHostedRuntimeUuid(runtimeId);
|
|
25
|
+
if (!rid) return String(suffix || '').trim();
|
|
26
|
+
const ch = String(channel || 'thirdfy')
|
|
27
|
+
.trim()
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
30
|
+
.slice(0, 32);
|
|
31
|
+
const tail = String(suffix || '').trim() || `write-${Date.now()}:${randomUUID()}`;
|
|
32
|
+
return `${HOSTED_RUNTIME_IDEMPOTENCY_NAMESPACE}:${rid}:${ch}:${tail}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function wrapWriteIdempotencyKeyForHostedRuntime(existingKey, runtimeId, _action, channel = 'thirdfy') {
|
|
36
|
+
const existing = String(existingKey || '').trim();
|
|
37
|
+
const rid = readHostedRuntimeUuid(runtimeId);
|
|
38
|
+
if (existing && isHostedRuntimeScopedIdempotencyKey(existing)) return existing;
|
|
39
|
+
if (rid) {
|
|
40
|
+
const suffix = existing || `cli:${Date.now()}:${randomUUID()}`;
|
|
41
|
+
return formatHostedRuntimeScopedIdempotencyKey({ runtimeId: rid, channel, suffix });
|
|
42
|
+
}
|
|
43
|
+
return existing;
|
|
44
|
+
}
|
|
@@ -35,6 +35,13 @@ function canonicalActionKey(action) {
|
|
|
35
35
|
.toLowerCase();
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function canonicalizeCatalogActionKey(value) {
|
|
39
|
+
return String(value || '')
|
|
40
|
+
.trim()
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/_/g, '-');
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
function inferActionProvider(action) {
|
|
39
46
|
// API catalog metadata (`providerId`, `domain`, `category`) is the source of truth.
|
|
40
47
|
// This fallback only keeps older catalog rows / older API deployments discoverable.
|
|
@@ -337,8 +344,12 @@ function getActionProvider(action) {
|
|
|
337
344
|
|
|
338
345
|
async function resolveActionSelection(ctx, flags) {
|
|
339
346
|
const requestedAction = requireFlag(flags, 'action', 'Missing --action');
|
|
340
|
-
const requestedLower = requestedAction.toLowerCase();
|
|
341
347
|
const actions = applyActionFilters(await getCachedActionsCatalog(ctx, flags), flags);
|
|
348
|
+
return resolveActionFromRows(requestedAction, actions, flags);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function resolveActionFromRows(requestedAction, actions, flags = {}) {
|
|
352
|
+
const requestedLower = String(requestedAction || '').toLowerCase();
|
|
342
353
|
const keyed = actions
|
|
343
354
|
.map((action) => ({
|
|
344
355
|
key: getActionKey(action),
|
|
@@ -381,11 +392,50 @@ async function resolveActionSelection(ctx, flags) {
|
|
|
381
392
|
);
|
|
382
393
|
}
|
|
383
394
|
|
|
395
|
+
const requestedCanonical = canonicalizeCatalogActionKey(requestedAction);
|
|
396
|
+
const canonicalKey = keyed.filter(
|
|
397
|
+
(entry) => canonicalizeCatalogActionKey(entry.key) === requestedCanonical
|
|
398
|
+
);
|
|
399
|
+
if (canonicalKey.length === 1) {
|
|
400
|
+
return {
|
|
401
|
+
requestedAction,
|
|
402
|
+
resolvedAction: canonicalKey[0].key,
|
|
403
|
+
resolvedActionMeta: canonicalKey[0].action || null,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (canonicalKey.length > 1) {
|
|
407
|
+
const identities = new Set(canonicalKey.map((entry) => canonicalizeCatalogActionKey(entry.key)));
|
|
408
|
+
if (identities.size === 1) {
|
|
409
|
+
const kebab = canonicalKey.find((entry) => entry.key.toLowerCase() === requestedCanonical);
|
|
410
|
+
const picked = kebab || canonicalKey[0];
|
|
411
|
+
return {
|
|
412
|
+
requestedAction,
|
|
413
|
+
resolvedAction: picked.key,
|
|
414
|
+
resolvedActionMeta: picked.action || null,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
throw new Error(
|
|
418
|
+
`Ambiguous --action "${requestedAction}". Multiple exact matches found: ${formatActionList(
|
|
419
|
+
canonicalKey.map((v) => v.key)
|
|
420
|
+
)}.`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
384
424
|
const exactAlias = keyed.filter((entry) => entry.aliases.some((a) => a.toLowerCase() === requestedLower));
|
|
385
425
|
if (exactAlias.length === 1) {
|
|
386
426
|
return { requestedAction, resolvedAction: exactAlias[0].key, resolvedActionMeta: exactAlias[0].action || null };
|
|
387
427
|
}
|
|
388
428
|
if (exactAlias.length > 1) {
|
|
429
|
+
const preferred = exactAlias.filter(
|
|
430
|
+
(entry) => canonicalizeCatalogActionKey(entry.key) === requestedCanonical
|
|
431
|
+
);
|
|
432
|
+
if (preferred.length === 1) {
|
|
433
|
+
return {
|
|
434
|
+
requestedAction,
|
|
435
|
+
resolvedAction: preferred[0].key,
|
|
436
|
+
resolvedActionMeta: preferred[0].action || null,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
389
439
|
const providerHint = flags.provider ? getTradingProviderHint(flags.provider) : null;
|
|
390
440
|
throw new Error(
|
|
391
441
|
`Ambiguous --action "${requestedAction}". Alias matches multiple actions: ${formatActionList(
|
|
@@ -420,6 +470,8 @@ async function resolveActionSelection(ctx, flags) {
|
|
|
420
470
|
normalizeProviderId,
|
|
421
471
|
applyActionFilters,
|
|
422
472
|
getActionProvider,
|
|
473
|
+
canonicalizeCatalogActionKey,
|
|
474
|
+
resolveActionFromRows,
|
|
423
475
|
resolveActionSelection,
|
|
424
476
|
};
|
|
425
477
|
}
|
|
@@ -1,63 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const POLYMARKET_PLACE_ACTIONS = new Set([
|
|
11
|
-
'place_polymarket_order',
|
|
12
|
-
'place-polymarket-order',
|
|
13
|
-
'place_prediction_order',
|
|
14
|
-
'place-prediction-order',
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
export function resolveEarnclawRuntimeId(env = process.env) {
|
|
18
|
-
const runtimeId = readUuid(env.THIRDFY_EXECUTION_RUNTIME_ID)
|
|
19
|
-
|| readUuid(env.EARNCLAW_RUNTIME_ID);
|
|
20
|
-
return runtimeId ? runtimeId.toLowerCase() : '';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function normalizePolymarketPlaceAction(action) {
|
|
24
|
-
return String(action || '')
|
|
25
|
-
.trim()
|
|
26
|
-
.toLowerCase()
|
|
27
|
-
.replace(/_/g, '-');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function isPolymarketPlaceAction(action) {
|
|
31
|
-
const key = String(action || '').trim().toLowerCase();
|
|
32
|
-
if (POLYMARKET_PLACE_ACTIONS.has(key)) return true;
|
|
33
|
-
return normalizePolymarketPlaceAction(key) === 'place-polymarket-order'
|
|
34
|
-
|| normalizePolymarketPlaceAction(key) === 'place-prediction-order';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* On EarnClaw-managed Hermes machines, ad-hoc CLI/MCP/dashboard tool calls can
|
|
39
|
-
* place Polymarket orders without pack execute-gate. Require an earnclaw-scoped
|
|
40
|
-
* idempotency key so only adapter/operator keys pass.
|
|
41
|
-
*/
|
|
42
|
-
export function assertEarnclawPolymarketWriteIdempotency({
|
|
43
|
-
action,
|
|
44
|
-
idempotencyKey,
|
|
45
|
-
paramsIdempotencyKey,
|
|
46
|
-
env = process.env,
|
|
47
|
-
} = {}) {
|
|
48
|
-
if (!isPolymarketPlaceAction(action)) return null;
|
|
49
|
-
const runtimeId = resolveEarnclawRuntimeId(env);
|
|
50
|
-
if (!runtimeId) return null;
|
|
51
|
-
|
|
52
|
-
const prefix = `earnclaw:${runtimeId}`;
|
|
53
|
-
const keys = [idempotencyKey, paramsIdempotencyKey]
|
|
54
|
-
.map((key) => String(key || '').trim())
|
|
55
|
-
.filter(Boolean);
|
|
56
|
-
if (keys.some((key) => key.toLowerCase().startsWith(prefix))) return null;
|
|
57
|
-
|
|
58
|
-
throw createCliError(
|
|
59
|
-
'EARNCLAW_PM_IDEMPOTENCY_REQUIRED',
|
|
60
|
-
`EarnClaw runtime ${runtimeId} requires Polymarket place orders to use idempotencyKey starting with "${prefix}". ` +
|
|
61
|
-
'Pack execute-gate and operator exits set this automatically. Bare CLI/dashboard tool writes are blocked.',
|
|
62
|
-
);
|
|
63
|
-
}
|
|
1
|
+
/** @deprecated Import from ./hostedRuntimeWriteGate.ts */
|
|
2
|
+
export {
|
|
3
|
+
assertHostedRuntimePolymarketWriteIdempotency as assertEarnclawPolymarketWriteIdempotency,
|
|
4
|
+
resolveHostedRuntimeIdFromEnv as resolveEarnclawRuntimeId,
|
|
5
|
+
isPolymarketPlaceAction,
|
|
6
|
+
normalizePolymarketPlaceAction,
|
|
7
|
+
} from './hostedRuntimeWriteGate.mjs';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createCliError } from '../../core/envelope.mjs';
|
|
2
|
+
import {
|
|
3
|
+
HOSTED_RUNTIME_IDEMPOTENCY_NAMESPACE,
|
|
4
|
+
readHostedRuntimeUuid,
|
|
5
|
+
} from '../../core/hostedRuntimeIdempotency.mjs';
|
|
6
|
+
|
|
7
|
+
const POLYMARKET_PLACE_ACTIONS = new Set([
|
|
8
|
+
'place_polymarket_order',
|
|
9
|
+
'place-polymarket-order',
|
|
10
|
+
'place_prediction_order',
|
|
11
|
+
'place-prediction-order',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export function resolveHostedRuntimeIdFromEnv(env = process.env) {
|
|
15
|
+
return (
|
|
16
|
+
readHostedRuntimeUuid(env.THIRDFY_EXECUTION_RUNTIME_ID)
|
|
17
|
+
|| readHostedRuntimeUuid(env.EARNCLAW_RUNTIME_ID)
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizePolymarketPlaceAction(action) {
|
|
22
|
+
return String(action || '')
|
|
23
|
+
.trim()
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.replace(/_/g, '-');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isPolymarketPlaceAction(action) {
|
|
29
|
+
const key = String(action || '').trim().toLowerCase();
|
|
30
|
+
if (POLYMARKET_PLACE_ACTIONS.has(key)) return true;
|
|
31
|
+
return normalizePolymarketPlaceAction(key) === 'place-polymarket-order'
|
|
32
|
+
|| normalizePolymarketPlaceAction(key) === 'place-prediction-order';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function assertHostedRuntimePolymarketWriteIdempotency({
|
|
36
|
+
action,
|
|
37
|
+
idempotencyKey,
|
|
38
|
+
paramsIdempotencyKey,
|
|
39
|
+
env = process.env,
|
|
40
|
+
} = {}) {
|
|
41
|
+
if (!isPolymarketPlaceAction(action)) return null;
|
|
42
|
+
const runtimeId = resolveHostedRuntimeIdFromEnv(env);
|
|
43
|
+
if (!runtimeId) return null;
|
|
44
|
+
|
|
45
|
+
const prefix = `${HOSTED_RUNTIME_IDEMPOTENCY_NAMESPACE}:${runtimeId}`;
|
|
46
|
+
const keys = [idempotencyKey, paramsIdempotencyKey]
|
|
47
|
+
.map((key) => String(key || '').trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
if (keys.some((key) => key.toLowerCase().startsWith(prefix))) return null;
|
|
50
|
+
|
|
51
|
+
throw createCliError(
|
|
52
|
+
'HOSTED_RUNTIME_PM_IDEMPOTENCY_REQUIRED',
|
|
53
|
+
`Hosted runtime ${runtimeId} requires Polymarket place orders to use idempotencyKey starting with "${prefix}". ` +
|
|
54
|
+
'Pack execute-gate and operator exits set this automatically. Bare CLI/dashboard tool writes are blocked.',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -12,6 +12,20 @@ export function managedReadExecuteErrorText(data) {
|
|
|
12
12
|
return String(data?.error || data?.message || data?.code || data?.blockedReason || '').toLowerCase();
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export function isEmptyQueuedExecuteIntent(data) {
|
|
16
|
+
const rec = data && typeof data === 'object' && !Array.isArray(data) ? data : null;
|
|
17
|
+
if (!rec) return false;
|
|
18
|
+
// Read payloads live in `data`. Do not mix those fields with outer batch defaults —
|
|
19
|
+
// `normalizeIntentResponse` materializes status=queued, executed=0, results=[].
|
|
20
|
+
const nested = rec.data && typeof rec.data === 'object' && !Array.isArray(rec.data) ? rec.data : rec;
|
|
21
|
+
const status = String(nested.status ?? '').toLowerCase();
|
|
22
|
+
const executed = Number(nested.executed);
|
|
23
|
+
const results = nested.results;
|
|
24
|
+
const emptyResults = Array.isArray(results) && results.length === 0;
|
|
25
|
+
const queued = status === 'queued' || (Number.isFinite(executed) && executed === 0);
|
|
26
|
+
return queued && emptyResults;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
export function shouldFallbackManagedReadToExecuteIntent({ isReadOnly, errorText } = {}) {
|
|
16
30
|
if (!isReadOnly) return false;
|
|
17
31
|
const err = String(errorText || '').toLowerCase();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
+
import { wrapWriteIdempotencyKeyForHostedRuntime } from '../../core/hostedRuntimeIdempotency.mjs';
|
|
3
|
+
import { resolveExecutionContextFromEnv } from '../../core/executionContext.mjs';
|
|
2
4
|
import { requireFlag, parseJsonFlag } from '../../core/args.mjs';
|
|
3
5
|
import { loadProfileConfig } from '../../core/context.mjs';
|
|
4
6
|
import { createCliError } from '../../core/envelope.mjs';
|
|
5
7
|
import { normalizeRunMode, normalizeManagedRunMode, normalizeHybridWalletMode } from '../../core/runMode.mjs';
|
|
6
|
-
import {
|
|
8
|
+
import { assertHostedRuntimePolymarketWriteIdempotency } from './hostedRuntimeWriteGate.mjs';
|
|
7
9
|
import { resolveTokenInForFunding } from '../../core/fundingTokenIn.mjs';
|
|
8
10
|
|
|
9
11
|
function isTruthyFlag(value) {
|
|
@@ -157,7 +159,14 @@ function buildManagedExecutePayload(flags, options) {
|
|
|
157
159
|
} else if (flags.idempotencyKey) {
|
|
158
160
|
payload.executionIdempotencyKey = String(flags.idempotencyKey);
|
|
159
161
|
}
|
|
160
|
-
|
|
162
|
+
if (payload.executionIdempotencyKey) {
|
|
163
|
+
payload.executionIdempotencyKey = wrapWriteIdempotencyKeyForHostedRuntime(
|
|
164
|
+
payload.executionIdempotencyKey,
|
|
165
|
+
resolveExecutionContextFromEnv().runtimeId,
|
|
166
|
+
payload.action,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
assertHostedRuntimePolymarketWriteIdempotency({
|
|
161
170
|
action: payload.action,
|
|
162
171
|
idempotencyKey: payload.executionIdempotencyKey,
|
|
163
172
|
paramsIdempotencyKey: managedParams?.idempotencyKey,
|
|
@@ -203,8 +212,15 @@ function buildIntentPayload(flags, options) {
|
|
|
203
212
|
} else if (flags.idempotencyKey) {
|
|
204
213
|
payload.idempotencyKey = String(flags.idempotencyKey);
|
|
205
214
|
}
|
|
215
|
+
if (payload.idempotencyKey) {
|
|
216
|
+
payload.idempotencyKey = wrapWriteIdempotencyKeyForHostedRuntime(
|
|
217
|
+
payload.idempotencyKey,
|
|
218
|
+
resolveExecutionContextFromEnv().runtimeId,
|
|
219
|
+
payload.action,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
206
222
|
if (!options.validationOnly) {
|
|
207
|
-
|
|
223
|
+
assertHostedRuntimePolymarketWriteIdempotency({
|
|
208
224
|
action: payload.action,
|
|
209
225
|
idempotencyKey: payload.idempotencyKey,
|
|
210
226
|
paramsIdempotencyKey: payload.params?.idempotencyKey,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { apiGet, apiPost } from '../../core/http.mjs';
|
|
13
13
|
import { withActionTimeout } from '../../core/actionTimeouts.mjs';
|
|
14
14
|
import { isReadOnlyResolvedAction } from '../../core/readOnlyActions.mjs';
|
|
15
|
-
import { shouldFallbackManagedReadToExecuteIntent } from './managedReadExecuteRail.mjs';
|
|
15
|
+
import { isEmptyQueuedExecuteIntent, shouldFallbackManagedReadToExecuteIntent } from './managedReadExecuteRail.mjs';
|
|
16
16
|
import { createRequire } from 'module';
|
|
17
17
|
|
|
18
18
|
const require = createRequire(import.meta.url);
|
|
@@ -506,6 +506,11 @@ async function executeManagedWalletRun(ctx, flags, resolved, options) {
|
|
|
506
506
|
const response = await apiPost(actionCtx, '/api/v1/agent/execute', payload);
|
|
507
507
|
if (shouldFallbackAgentWalletToExecuteIntent(resolved, response)) {
|
|
508
508
|
const intentResult = await runReadOnlyActionViaIntentRail(ctx, flags, resolved, options, preflight);
|
|
509
|
+
if (isEmptyQueuedExecuteIntent(intentResult.raw || intentResult)) {
|
|
510
|
+
intentResult.success = false;
|
|
511
|
+
intentResult.status = 'failed';
|
|
512
|
+
intentResult.error = intentResult.error || 'MANAGED_READ_EMPTY_EXECUTE_INTENT';
|
|
513
|
+
}
|
|
509
514
|
intentResult.executionWalletAddress =
|
|
510
515
|
preflight?.executionWalletAddress ||
|
|
511
516
|
response?.executionWalletAddress ||
|