pandora-cli-skills 1.1.62 → 1.1.63
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/SKILL.md +1 -1
- package/cli/lib/agent_contract_registry.cjs +8 -3
- package/cli/lib/error_recovery_service.cjs +24 -3
- package/cli/lib/mcp_tool_registry.cjs +4 -1
- package/cli/lib/mirror_command_service.cjs +2 -2
- package/cli/lib/mirror_handlers/deploy.cjs +2 -2
- package/cli/lib/mirror_service.cjs +328 -24
- package/cli/lib/parsers/mirror_deploy_flags.cjs +6 -0
- package/cli/lib/parsers/mirror_go_flags.cjs +6 -0
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -1923,7 +1923,7 @@ const commandContracts = [
|
|
|
1923
1923
|
name: 'mirror.deploy',
|
|
1924
1924
|
summary: 'Deploy a mirror market from selector or plan in dry-run or execute mode.',
|
|
1925
1925
|
usage:
|
|
1926
|
-
'pandora [--output table|json] mirror deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--oracle <address>] [--factory <address>] [--usdc <address>] [--distribution-yes <parts>] [--distribution-no <parts>] [--sources <url...>] [--manifest-file <path>] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--min-close-lead-seconds <n>]',
|
|
1926
|
+
'pandora [--output table|json] mirror deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--oracle <address>] [--factory <address>] [--usdc <address>] [--distribution-yes <parts>] [--distribution-no <parts>] [--sources <url...>] [--validation-ticket <ticket>] [--manifest-file <path>] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--min-close-lead-seconds <n>]',
|
|
1927
1927
|
emits: ['mirror.deploy', 'mirror.deploy.help'],
|
|
1928
1928
|
dataSchema: '#/definitions/MirrorDeployPayload',
|
|
1929
1929
|
mcpExposed: true,
|
|
@@ -1933,7 +1933,8 @@ const commandContracts = [
|
|
|
1933
1933
|
executeRequiresValidation: true,
|
|
1934
1934
|
notes: [
|
|
1935
1935
|
'Mirror deploy dry-run returns the exact Pandora deployment payload and required validation ticket.',
|
|
1936
|
-
'
|
|
1936
|
+
'Mirror deploy never auto-copies Polymarket URLs into sources; pass independent public resolution URLs with --sources.',
|
|
1937
|
+
'Run agent.market.validate on that final payload before rerunning mirror.deploy with execute mode, then pass --validation-ticket locally or agentPreflight in MCP.',
|
|
1937
1938
|
],
|
|
1938
1939
|
},
|
|
1939
1940
|
mcp: {
|
|
@@ -1961,6 +1962,7 @@ const commandContracts = [
|
|
|
1961
1962
|
'distribution-yes': numberSchema('Initial YES distribution parts.', { minimum: 0 }),
|
|
1962
1963
|
'distribution-no': numberSchema('Initial NO distribution parts.', { minimum: 0 }),
|
|
1963
1964
|
sources: flexibleArraySchema(stringSchema(), 'Source URL list.'),
|
|
1965
|
+
'validation-ticket': stringSchema('Ticket returned by agent.market.validate for the exact final payload (CLI execute mode).'),
|
|
1964
1966
|
'manifest-file': stringSchema('Mirror manifest path.'),
|
|
1965
1967
|
'polymarket-host': stringSchema('Polymarket host override.'),
|
|
1966
1968
|
'polymarket-gamma-url': stringSchema('Polymarket Gamma API base URL.'),
|
|
@@ -2089,7 +2091,7 @@ const commandContracts = [
|
|
|
2089
2091
|
name: 'mirror.go',
|
|
2090
2092
|
summary: 'Run mirror deploy, verify, and optional sync workflow.',
|
|
2091
2093
|
usage:
|
|
2092
|
-
'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--paper|--dry-run|--execute-live|--execute] [--auto-sync] [--sync-once] [--sync-interval-ms <ms>] [--max-open-exposure-usdc <amount>] [--max-trades-per-day <n>] [--polymarket-rpc-url <url>] [--manifest-file <path>] [--dotenv-path <path>] [--rpc-url <url>] [--private-key <hex>]',
|
|
2094
|
+
'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--paper|--dry-run|--execute-live|--execute] [--auto-sync] [--sync-once] [--sync-interval-ms <ms>] [--max-open-exposure-usdc <amount>] [--max-trades-per-day <n>] [--polymarket-rpc-url <url>] [--sources <url...>] [--validation-ticket <ticket>] [--manifest-file <path>] [--dotenv-path <path>] [--rpc-url <url>] [--private-key <hex>]',
|
|
2093
2095
|
emits: ['mirror.go', 'mirror.go.help'],
|
|
2094
2096
|
dataSchema: '#/definitions/MirrorDeployPayload',
|
|
2095
2097
|
mcpExposed: true,
|
|
@@ -2099,6 +2101,7 @@ const commandContracts = [
|
|
|
2099
2101
|
executeRequiresValidation: true,
|
|
2100
2102
|
notes: [
|
|
2101
2103
|
'Mirror go inherits the exact market payload from its deploy stage; use the returned validation ticket from paper/dry-run output.',
|
|
2104
|
+
'When mirror go will execute a fresh deploy, provide independent public --sources and a matching validation ticket.',
|
|
2102
2105
|
'Run agent.market.validate on that exact payload before rerunning mirror.go with execute or execute-live.',
|
|
2103
2106
|
],
|
|
2104
2107
|
},
|
|
@@ -2125,6 +2128,8 @@ const commandContracts = [
|
|
|
2125
2128
|
'max-open-exposure-usdc': numberSchema('Maximum open exposure in USDC.', { minimum: 0 }),
|
|
2126
2129
|
'max-trades-per-day': integerSchema('Maximum daily trade count.', { minimum: 0 }),
|
|
2127
2130
|
'polymarket-rpc-url': stringSchema('Polygon RPC URL for Polymarket preflight.'),
|
|
2131
|
+
sources: flexibleArraySchema(stringSchema(), 'Independent public source URL list.'),
|
|
2132
|
+
'validation-ticket': stringSchema('Ticket returned by agent.market.validate for the exact final payload (CLI execute mode).'),
|
|
2128
2133
|
'manifest-file': stringSchema('Mirror manifest path.'),
|
|
2129
2134
|
'dotenv-path': stringSchema('Env file path.'),
|
|
2130
2135
|
'rpc-url': commonFlags.rpcUrl,
|
|
@@ -44,8 +44,14 @@ function buildPolymarketPreflightCommand(cliName) {
|
|
|
44
44
|
return `${cliName} polymarket preflight`;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function buildMirrorDeployRetryCommand(cliName) {
|
|
48
|
-
|
|
47
|
+
function buildMirrorDeployRetryCommand(cliName, details) {
|
|
48
|
+
const selectorPlan = cleanToken(details && details.planFile, '')
|
|
49
|
+
? `--plan-file ${cleanToken(details && details.planFile, '<plan-file>')}`
|
|
50
|
+
: '--plan-file <plan-file>';
|
|
51
|
+
const includeSourcesPlaceholder =
|
|
52
|
+
details
|
|
53
|
+
&& (details.requiredMinimum || details.invalidSources || details.dependentSources || details.requiredValidation);
|
|
54
|
+
return `${cliName} mirror deploy --dry-run ${selectorPlan}${includeSourcesPlaceholder ? ' --sources <url1> <url2>' : ''}`;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
function buildMirrorVerifyRetryCommand(cliName, details) {
|
|
@@ -232,12 +238,27 @@ function createErrorRecoveryService(options = {}) {
|
|
|
232
238
|
command: buildPolymarketPreflightCommand(cliName),
|
|
233
239
|
retryable: true,
|
|
234
240
|
};
|
|
241
|
+
case 'MIRROR_VALIDATION_REQUIRED':
|
|
242
|
+
case 'MIRROR_VALIDATION_MISMATCH':
|
|
243
|
+
return {
|
|
244
|
+
action: 'Run market validation for the exact mirror payload and reuse the returned ticket',
|
|
245
|
+
command: buildAgentMarketValidateCommand(cliName, details),
|
|
246
|
+
retryable: true,
|
|
247
|
+
};
|
|
248
|
+
case 'MIRROR_RULES_FORMAT_INVALID':
|
|
249
|
+
case 'MIRROR_SOURCES_REQUIRED':
|
|
250
|
+
case 'MIRROR_SOURCES_INVALID':
|
|
251
|
+
return {
|
|
252
|
+
action: 'Rebuild mirror deploy inputs in dry-run mode and provide explicit independent sources',
|
|
253
|
+
command: buildMirrorDeployRetryCommand(cliName, details),
|
|
254
|
+
retryable: true,
|
|
255
|
+
};
|
|
235
256
|
case 'MIRROR_DEPLOY_FAILED':
|
|
236
257
|
case 'MIRROR_GO_FAILED':
|
|
237
258
|
case 'MIRROR_GO_PREFLIGHT_FAILED':
|
|
238
259
|
return {
|
|
239
260
|
action: 'Re-run mirror deploy/verify in dry-run mode',
|
|
240
|
-
command: buildMirrorDeployRetryCommand(cliName),
|
|
261
|
+
command: buildMirrorDeployRetryCommand(cliName, details),
|
|
241
262
|
retryable: true,
|
|
242
263
|
};
|
|
243
264
|
case 'MIRROR_GO_VERIFY_FAILED':
|
|
@@ -225,6 +225,7 @@ function toToolDescriptor(definition) {
|
|
|
225
225
|
canonicalTool: definition.canonicalTool || definition.aliasOf || definition.name,
|
|
226
226
|
aliasOf: definition.aliasOf || null,
|
|
227
227
|
preferred: definition.preferred !== false,
|
|
228
|
+
compatibilityAlias: Boolean(definition.aliasOf),
|
|
228
229
|
mutating: Boolean(definition.mutating),
|
|
229
230
|
longRunningBlocked: Boolean(definition.longRunningBlocked),
|
|
230
231
|
controlInputNames: Array.isArray(definition.controlInputNames) ? [...definition.controlInputNames] : [],
|
|
@@ -238,7 +239,9 @@ function toToolDescriptor(definition) {
|
|
|
238
239
|
|
|
239
240
|
return {
|
|
240
241
|
name: definition.name,
|
|
241
|
-
description: definition.
|
|
242
|
+
description: definition.aliasOf
|
|
243
|
+
? `${definition.description} Compatibility alias for ${xPandora.canonicalTool}; prefer ${xPandora.canonicalTool}.`
|
|
244
|
+
: definition.description,
|
|
242
245
|
inputSchema: {
|
|
243
246
|
...inputSchema,
|
|
244
247
|
xPandora,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @type {string}
|
|
5
5
|
*/
|
|
6
6
|
const MIRROR_GO_USAGE =
|
|
7
|
-
'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--paper|--dry-run|--execute-live|--execute] [--auto-sync] [--sync-once] [--sync-interval-ms <ms>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--chain-id <id>] [--rpc-url <url>] [--polymarket-rpc-url <url>] [--private-key <hex>] [--funder <address>] [--usdc <address>] [--oracle <address>] [--factory <address>] [--sources <url...>] [--manifest-file <path>] [--trust-deploy] [--skip-gate] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--with-rules] [--include-similarity] [--min-close-lead-seconds <n>]';
|
|
7
|
+
'pandora [--output table|json] mirror go --polymarket-market-id <id>|--polymarket-slug <slug> [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--paper|--dry-run|--execute-live|--execute] [--auto-sync] [--sync-once] [--sync-interval-ms <ms>] [--hedge-ratio <n>] [--no-hedge] [--max-rebalance-usdc <n>] [--max-hedge-usdc <n>] [--max-open-exposure-usdc <n>] [--max-trades-per-day <n>] [--cooldown-ms <ms>] [--chain-id <id>] [--rpc-url <url>] [--polymarket-rpc-url <url>] [--private-key <hex>] [--funder <address>] [--usdc <address>] [--oracle <address>] [--factory <address>] [--sources <url...>] [--validation-ticket <ticket>] [--manifest-file <path>] [--trust-deploy] [--skip-gate] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--with-rules] [--include-similarity] [--min-close-lead-seconds <n>]';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Canonical usage string for `mirror sync`.
|
|
@@ -97,7 +97,7 @@ function createRunMirrorCommand(deps) {
|
|
|
97
97
|
' plan --source polymarket --polymarket-market-id <id>|--polymarket-slug <slug> [--chain-id <id>] [--target-slippage-bps <n>] [--turnover-target <n>] [--depth-slippage-bps <n>] [--safety-multiplier <n>] [--min-liquidity-usdc <n>] [--max-liquidity-usdc <n>] [--with-rules] [--include-similarity] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>]',
|
|
98
98
|
);
|
|
99
99
|
console.log(
|
|
100
|
-
' deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--oracle <address>] [--factory <address>] [--usdc <address>] [--distribution-yes <parts>] [--distribution-no <parts>] [--sources <url...>] [--manifest-file <path>] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--min-close-lead-seconds <n>]',
|
|
100
|
+
' deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--chain-id <id>] [--rpc-url <url>] [--private-key <hex>] [--oracle <address>] [--factory <address>] [--usdc <address>] [--distribution-yes <parts>] [--distribution-no <parts>] [--sources <url...>] [--validation-ticket <ticket>] [--manifest-file <path>] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>] [--min-close-lead-seconds <n>]',
|
|
101
101
|
);
|
|
102
102
|
console.log(
|
|
103
103
|
' verify --pandora-market-address <address>|--market-address <address> --polymarket-market-id <id>|--polymarket-slug <slug> [--trust-deploy] [--manifest-file <path>] [--include-similarity] [--with-rules] [--allow-rule-mismatch] [--polymarket-host <url>] [--polymarket-gamma-url <url>] [--polymarket-gamma-mock-url <url>] [--polymarket-mock-url <url>]',
|
|
@@ -24,12 +24,12 @@ module.exports = async function handleMirrorDeploy({ shared, context, deps }) {
|
|
|
24
24
|
context.outputMode,
|
|
25
25
|
'mirror.deploy.help',
|
|
26
26
|
commandHelpPayload(
|
|
27
|
-
'pandora [--output table|json] mirror deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--manifest-file <path>] [--min-close-lead-seconds <n>]',
|
|
27
|
+
'pandora [--output table|json] mirror deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--sources <url...>] [--validation-ticket <ticket>] [--manifest-file <path>] [--min-close-lead-seconds <n>]',
|
|
28
28
|
),
|
|
29
29
|
);
|
|
30
30
|
} else {
|
|
31
31
|
console.log(
|
|
32
|
-
'Usage: pandora [--output table|json] mirror deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--manifest-file <path>] [--min-close-lead-seconds <n>]',
|
|
32
|
+
'Usage: pandora [--output table|json] mirror deploy --plan-file <path>|--polymarket-market-id <id>|--polymarket-slug <slug> --dry-run|--execute [--liquidity-usdc <n>] [--fee-tier <500-50000>] [--max-imbalance <n>] [--arbiter <address>] [--category <n>] [--sources <url...>] [--validation-ticket <ticket>] [--manifest-file <path>] [--min-close-lead-seconds <n>]',
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
return;
|
|
@@ -10,8 +10,10 @@ const {
|
|
|
10
10
|
hashRules,
|
|
11
11
|
preloadPandoraMatchCandidates,
|
|
12
12
|
} = require('./mirror_verify_service.cjs');
|
|
13
|
+
const { buildRequiredAgentMarketValidation } = require('./agent_market_prompt_service.cjs');
|
|
13
14
|
const { deployPandoraAmmMarket } = require('./pandora_deploy_service.cjs');
|
|
14
15
|
const { defaultManifestFile, upsertPair } = require('./mirror_manifest_store.cjs');
|
|
16
|
+
const { isMcpMode } = require('./shared/mcp_path_guard.cjs');
|
|
15
17
|
const { round } = require('./shared/utils.cjs');
|
|
16
18
|
|
|
17
19
|
const MIRROR_PLAN_SCHEMA_VERSION = '1.0.0';
|
|
@@ -38,19 +40,310 @@ function normalizeSources(value) {
|
|
|
38
40
|
.filter(Boolean);
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
function normalizeComparableText(value) {
|
|
44
|
+
return String(value || '')
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.replace(/&/g, ' and ')
|
|
47
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
48
|
+
.replace(/\b(the|fc|cf|sc|ac|club)\b/g, ' ')
|
|
49
|
+
.replace(/\s+/g, ' ')
|
|
50
|
+
.trim();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasPandoraBinaryRules(value) {
|
|
54
|
+
const text = String(value || '');
|
|
55
|
+
return /(^|\n)\s*YES\s*:/i.test(text) && /(^|\n)\s*NO\s*:/i.test(text);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function sanitizeParticipantLabel(value) {
|
|
59
|
+
return String(value || '')
|
|
60
|
+
.replace(/^["'`(\[]+/, '')
|
|
61
|
+
.replace(/["'`)\].,:;!?]+$/, '')
|
|
62
|
+
.trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractMatchParticipants(question) {
|
|
66
|
+
const text = String(question || '').trim();
|
|
67
|
+
if (!text) return [];
|
|
68
|
+
|
|
69
|
+
const patterns = [
|
|
70
|
+
/\bwill\s+(.+?)\s+(?:beat|defeat|top|topple|upset|outscore)\s+(.+?)(?:\?|$)/i,
|
|
71
|
+
/^(.+?)\s+(?:vs\.?|v\.?|@|at)\s+(.+?)(?:\?|$)/i,
|
|
72
|
+
/\bbetween\s+(.+?)\s+and\s+(.+?)(?:\?|$)/i,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
for (const pattern of patterns) {
|
|
76
|
+
const match = text.match(pattern);
|
|
77
|
+
if (!match) continue;
|
|
78
|
+
const left = sanitizeParticipantLabel(match[1]);
|
|
79
|
+
const right = sanitizeParticipantLabel(match[2]);
|
|
80
|
+
if (left && right) {
|
|
81
|
+
return Array.from(new Set([left, right]));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function extractResolveToSelection(description) {
|
|
89
|
+
const text = String(description || '').trim();
|
|
90
|
+
if (!text) return null;
|
|
91
|
+
|
|
92
|
+
const patterns = [
|
|
93
|
+
/\b(?:this market )?resolve(?:s|d)?\s+to\s+([^.;\n]+)/i,
|
|
94
|
+
/\bresolve(?:s|d)?\s+(?:in favor of|for)\s+([^.;\n]+)/i,
|
|
95
|
+
/\bwinner(?:\s+is)?\s*:\s*([^.;\n]+)/i,
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const pattern of patterns) {
|
|
99
|
+
const match = text.match(pattern);
|
|
100
|
+
if (match && match[1]) {
|
|
101
|
+
const candidate = sanitizeParticipantLabel(match[1]);
|
|
102
|
+
if (candidate) return candidate;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function findBestParticipantMatch(selection, participants) {
|
|
110
|
+
const normalizedSelection = normalizeComparableText(selection);
|
|
111
|
+
if (!normalizedSelection || !Array.isArray(participants) || !participants.length) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let best = null;
|
|
116
|
+
let bestScore = 0;
|
|
117
|
+
const selectionTokens = new Set(normalizedSelection.split(' ').filter(Boolean));
|
|
118
|
+
for (const participant of participants) {
|
|
119
|
+
const normalizedParticipant = normalizeComparableText(participant);
|
|
120
|
+
if (!normalizedParticipant) continue;
|
|
121
|
+
if (
|
|
122
|
+
normalizedSelection === normalizedParticipant ||
|
|
123
|
+
normalizedSelection.includes(normalizedParticipant) ||
|
|
124
|
+
normalizedParticipant.includes(normalizedSelection)
|
|
125
|
+
) {
|
|
126
|
+
return participant;
|
|
127
|
+
}
|
|
128
|
+
const participantTokens = new Set(normalizedParticipant.split(' ').filter(Boolean));
|
|
129
|
+
const overlap = [...selectionTokens].filter((token) => participantTokens.has(token)).length;
|
|
130
|
+
const score = overlap / Math.max(selectionTokens.size, participantTokens.size, 1);
|
|
131
|
+
if (score > bestScore) {
|
|
132
|
+
best = participant;
|
|
133
|
+
bestScore = score;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return bestScore >= 0.5 ? best : null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildWinnerRules(selection, opponent) {
|
|
141
|
+
const yesBranch = `YES: The official winner of the event described in the market question is ${selection}.`;
|
|
142
|
+
const noBranch = opponent
|
|
143
|
+
? `NO: The official winner is ${opponent}, or the event ends in a draw if an official draw is possible.`
|
|
144
|
+
: `NO: ${selection} is not the official winner of the event described in the market question.`;
|
|
145
|
+
const edgeBranch =
|
|
146
|
+
'EDGE: If the event is canceled, postponed, abandoned, or no official result is declared by targetTimestamp, resolve NO.';
|
|
147
|
+
return [yesBranch, noBranch, edgeBranch].join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildQuestionFallbackRules(question) {
|
|
151
|
+
const normalizedQuestion = String(question || '').trim().replace(/\?+$/, '');
|
|
152
|
+
if (!normalizedQuestion) {
|
|
153
|
+
return 'YES: The market question resolves true by targetTimestamp.\nNO: The market question does not resolve true by targetTimestamp.\nEDGE: If the event is canceled, postponed, abandoned, or no official result is declared by targetTimestamp, resolve NO.';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const affirmative = normalizedQuestion.replace(/^will\s+/i, '').trim();
|
|
157
|
+
return [
|
|
158
|
+
`YES: ${affirmative.charAt(0).toUpperCase()}${affirmative.slice(1)}.`,
|
|
159
|
+
`NO: It is not true that ${affirmative}.`,
|
|
160
|
+
'EDGE: If the event is canceled, postponed, abandoned, or no official result is declared by targetTimestamp, resolve NO.',
|
|
161
|
+
].join('\n');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function assertPandoraBinaryRules(rulesText, details = {}) {
|
|
165
|
+
if (hasPandoraBinaryRules(rulesText)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
throw createServiceError(
|
|
169
|
+
'MIRROR_RULES_FORMAT_INVALID',
|
|
170
|
+
'Mirror rules must use explicit Pandora YES:/NO: branches before deploy. Re-run mirror plan --with-rules or pass --rules with binary Pandora rules.',
|
|
171
|
+
details,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isPolymarketSourceUrl(value) {
|
|
176
|
+
try {
|
|
177
|
+
const parsed = new URL(String(value || '').trim());
|
|
178
|
+
return /(^|\.)polymarket\.com$/i.test(parsed.hostname);
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function assertIndependentMirrorSources(sources) {
|
|
185
|
+
const normalized = normalizeSources(sources);
|
|
186
|
+
const invalidSources = [];
|
|
187
|
+
const dependentSources = [];
|
|
188
|
+
const distinctSources = new Set();
|
|
189
|
+
const distinctHosts = new Set();
|
|
190
|
+
|
|
191
|
+
for (const source of normalized) {
|
|
192
|
+
try {
|
|
193
|
+
const parsed = new URL(source);
|
|
194
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
195
|
+
invalidSources.push(source);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (isPolymarketSourceUrl(source)) {
|
|
199
|
+
dependentSources.push(source);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
distinctSources.add(parsed.toString());
|
|
203
|
+
distinctHosts.add(parsed.hostname.toLowerCase());
|
|
204
|
+
} catch {
|
|
205
|
+
invalidSources.push(source);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!normalized.length) {
|
|
210
|
+
throw createServiceError(
|
|
211
|
+
'MIRROR_SOURCES_REQUIRED',
|
|
212
|
+
'Mirror deploy requires explicit independent resolution sources via --sources. Polymarket URLs are never used automatically.',
|
|
213
|
+
{
|
|
214
|
+
requiredMinimum: 2,
|
|
215
|
+
normalizedCount: 0,
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (invalidSources.length) {
|
|
221
|
+
throw createServiceError(
|
|
222
|
+
'MIRROR_SOURCES_INVALID',
|
|
223
|
+
'--sources must contain valid http(s) URLs.',
|
|
224
|
+
{
|
|
225
|
+
invalidSources,
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (dependentSources.length) {
|
|
231
|
+
throw createServiceError(
|
|
232
|
+
'MIRROR_SOURCES_INVALID',
|
|
233
|
+
'Mirror deploy requires independent resolution sources. Polymarket URLs are not allowed in --sources.',
|
|
234
|
+
{
|
|
235
|
+
dependentSources,
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (normalized.length < 2) {
|
|
241
|
+
throw createServiceError(
|
|
242
|
+
'MIRROR_SOURCES_REQUIRED',
|
|
243
|
+
'Mirror deploy requires at least two independent resolution sources in --sources.',
|
|
244
|
+
{
|
|
245
|
+
requiredMinimum: 2,
|
|
246
|
+
normalizedCount: normalized.length,
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (distinctSources.size < 2 || distinctHosts.size < 2) {
|
|
252
|
+
throw createServiceError(
|
|
253
|
+
'MIRROR_SOURCES_REQUIRED',
|
|
254
|
+
'Mirror deploy requires at least two independent resolution sources from different hosts in --sources.',
|
|
255
|
+
{
|
|
256
|
+
requiredMinimum: 2,
|
|
257
|
+
normalizedCount: normalized.length,
|
|
258
|
+
distinctSourceCount: distinctSources.size,
|
|
259
|
+
distinctHostCount: distinctHosts.size,
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return normalized;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function assertMirrorValidationTicket({ execute, question, rules, sources, targetTimestamp, validationTicket }) {
|
|
268
|
+
const requiredValidation = buildRequiredAgentMarketValidation({
|
|
269
|
+
question,
|
|
270
|
+
rules,
|
|
271
|
+
sources,
|
|
272
|
+
targetTimestamp,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (!execute || isMcpMode()) {
|
|
276
|
+
return {
|
|
277
|
+
requiredValidation,
|
|
278
|
+
agentValidation: null,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const providedTicket = String(validationTicket || '').trim();
|
|
283
|
+
if (!providedTicket) {
|
|
284
|
+
throw createServiceError(
|
|
285
|
+
'MIRROR_VALIDATION_REQUIRED',
|
|
286
|
+
'mirror execute requires --validation-ticket from agent market validate for the exact final mirror payload.',
|
|
287
|
+
{
|
|
288
|
+
requiredValidation,
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (providedTicket !== requiredValidation.ticket) {
|
|
294
|
+
throw createServiceError(
|
|
295
|
+
'MIRROR_VALIDATION_MISMATCH',
|
|
296
|
+
'Provided --validation-ticket does not match the exact final mirror market payload.',
|
|
297
|
+
{
|
|
298
|
+
expectedTicket: requiredValidation.ticket,
|
|
299
|
+
receivedTicket: providedTicket,
|
|
300
|
+
requiredValidation,
|
|
301
|
+
},
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
requiredValidation,
|
|
307
|
+
agentValidation: {
|
|
308
|
+
ok: true,
|
|
309
|
+
ticket: providedTicket,
|
|
310
|
+
decision: 'PASS',
|
|
311
|
+
summary: 'Validated via CLI ticket gate.',
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
41
316
|
function buildRuleTemplate(sourceMarket) {
|
|
42
317
|
const diagnostics = [];
|
|
318
|
+
const sourceQuestion = String(sourceMarket && sourceMarket.question ? sourceMarket.question : '').trim();
|
|
43
319
|
const sourceDescription = String(sourceMarket && sourceMarket.description ? sourceMarket.description : '').trim();
|
|
44
|
-
if (sourceDescription) {
|
|
320
|
+
if (sourceDescription && hasPandoraBinaryRules(sourceDescription)) {
|
|
45
321
|
return {
|
|
46
322
|
rulesText: sourceDescription,
|
|
47
323
|
diagnostics,
|
|
48
324
|
};
|
|
49
325
|
}
|
|
50
326
|
|
|
51
|
-
|
|
327
|
+
const selectedOutcome = extractResolveToSelection(sourceDescription);
|
|
328
|
+
if (selectedOutcome) {
|
|
329
|
+
const participants = extractMatchParticipants(sourceQuestion);
|
|
330
|
+
const selectedParticipant = findBestParticipantMatch(selectedOutcome, participants) || selectedOutcome;
|
|
331
|
+
const opposingParticipant =
|
|
332
|
+
participants.find((participant) => normalizeComparableText(participant) !== normalizeComparableText(selectedParticipant)) || null;
|
|
333
|
+
|
|
334
|
+
diagnostics.push('Translated source market resolution text into Pandora YES/NO rules.');
|
|
335
|
+
if (participants.length >= 2 && !opposingParticipant) {
|
|
336
|
+
diagnostics.push('Unable to confidently determine the opposing side; NO branch resolves when the selected side does not win.');
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
rulesText: buildWinnerRules(selectedParticipant, opposingParticipant),
|
|
340
|
+
diagnostics,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
diagnostics.push('Source rules were not already in Pandora YES/NO format; generated fallback binary rule template from the source question.');
|
|
52
345
|
return {
|
|
53
|
-
rulesText:
|
|
346
|
+
rulesText: buildQuestionFallbackRules(sourceQuestion || 'the source condition'),
|
|
54
347
|
diagnostics,
|
|
55
348
|
};
|
|
56
349
|
}
|
|
@@ -251,15 +544,31 @@ async function deployMirror(options = {}) {
|
|
|
251
544
|
}
|
|
252
545
|
|
|
253
546
|
const diagnostics = [];
|
|
254
|
-
const
|
|
547
|
+
const question = String(planData.sourceMarket && planData.sourceMarket.question ? planData.sourceMarket.question : '').trim();
|
|
548
|
+
const targetTimestamp = Number(planData.sourceMarket && planData.sourceMarket.closeTimestamp);
|
|
549
|
+
const refreshedRuleTemplate = buildRuleTemplate({
|
|
550
|
+
...(planData.sourceMarket || {}),
|
|
551
|
+
description:
|
|
552
|
+
(planData.rules && (planData.rules.sourceRules || planData.rules.proposedPandoraRules))
|
|
553
|
+
|| (planData.sourceMarket && planData.sourceMarket.description)
|
|
554
|
+
|| '',
|
|
555
|
+
});
|
|
556
|
+
let sourceRulesText = String(
|
|
255
557
|
options.rules ||
|
|
256
558
|
(planData.rules && (planData.rules.proposedPandoraRules || planData.rules.sourceRules)) ||
|
|
257
559
|
(planData.sourceMarket && planData.sourceMarket.description) ||
|
|
258
560
|
'',
|
|
259
561
|
).trim();
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
562
|
+
if (!options.rules && !hasPandoraBinaryRules(sourceRulesText) && hasPandoraBinaryRules(refreshedRuleTemplate.rulesText)) {
|
|
563
|
+
sourceRulesText = refreshedRuleTemplate.rulesText;
|
|
564
|
+
diagnostics.push('Upgraded non-binary source rules to Pandora YES/NO format during deploy.');
|
|
565
|
+
}
|
|
566
|
+
assertPandoraBinaryRules(sourceRulesText, {
|
|
567
|
+
question,
|
|
568
|
+
sourceRules: planData.rules && planData.rules.sourceRules ? planData.rules.sourceRules : null,
|
|
569
|
+
suggestedRules: refreshedRuleTemplate.rulesText,
|
|
570
|
+
});
|
|
571
|
+
diagnostics.push(...refreshedRuleTemplate.diagnostics);
|
|
263
572
|
|
|
264
573
|
const liquidityUsdc =
|
|
265
574
|
options.liquidityUsdc !== null && options.liquidityUsdc !== undefined
|
|
@@ -275,20 +584,15 @@ async function deployMirror(options = {}) {
|
|
|
275
584
|
? Number(options.distributionNo)
|
|
276
585
|
: Number(planData.distributionHint && planData.distributionHint.distributionNo);
|
|
277
586
|
|
|
278
|
-
const sources =
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
if (!options.sourcesProvided && sources.length < 2) {
|
|
290
|
-
diagnostics.push('Using fallback source URLs because explicit --sources were not provided.');
|
|
291
|
-
}
|
|
587
|
+
const sources = assertIndependentMirrorSources(options.sources);
|
|
588
|
+
const validationGate = assertMirrorValidationTicket({
|
|
589
|
+
execute: Boolean(options.execute),
|
|
590
|
+
question,
|
|
591
|
+
rules: sourceRulesText,
|
|
592
|
+
sources,
|
|
593
|
+
targetTimestamp,
|
|
594
|
+
validationTicket: options.validationTicket,
|
|
595
|
+
});
|
|
292
596
|
|
|
293
597
|
let deployPayload;
|
|
294
598
|
try {
|
|
@@ -302,7 +606,7 @@ async function deployMirror(options = {}) {
|
|
|
302
606
|
usdc: options.usdc,
|
|
303
607
|
question,
|
|
304
608
|
rules: sourceRulesText,
|
|
305
|
-
sources
|
|
609
|
+
sources,
|
|
306
610
|
targetTimestamp,
|
|
307
611
|
minCloseLeadSeconds: Number.isFinite(Number(options.minCloseLeadSeconds))
|
|
308
612
|
? Number(options.minCloseLeadSeconds)
|
|
@@ -393,8 +697,8 @@ async function deployMirror(options = {}) {
|
|
|
393
697
|
planDigest: planData.planDigest || buildPlanDigest(planData),
|
|
394
698
|
deploymentArgs: deployPayload.deploymentArgs,
|
|
395
699
|
dryRun: deployPayload.mode === 'dry-run',
|
|
396
|
-
requiredValidation: deployPayload.requiredValidation || null,
|
|
397
|
-
agentValidation: deployPayload.agentValidation || null,
|
|
700
|
+
requiredValidation: deployPayload.requiredValidation || validationGate.requiredValidation || null,
|
|
701
|
+
agentValidation: deployPayload.agentValidation || validationGate.agentValidation || null,
|
|
398
702
|
tx: deployPayload.tx,
|
|
399
703
|
pandora: deployPayload.pandora,
|
|
400
704
|
postDeployChecks,
|
|
@@ -120,6 +120,7 @@ function createParseMirrorDeployFlags(deps) {
|
|
|
120
120
|
distributionYesPct: null,
|
|
121
121
|
distributionNoPct: null,
|
|
122
122
|
rules: null,
|
|
123
|
+
validationTicket: null,
|
|
123
124
|
polymarketHost: null,
|
|
124
125
|
polymarketGammaUrl: null,
|
|
125
126
|
polymarketGammaMockUrl: null,
|
|
@@ -251,6 +252,11 @@ function createParseMirrorDeployFlags(deps) {
|
|
|
251
252
|
i += 1;
|
|
252
253
|
continue;
|
|
253
254
|
}
|
|
255
|
+
if (token === '--validation-ticket') {
|
|
256
|
+
options.validationTicket = requireFlagValue(args, i, '--validation-ticket');
|
|
257
|
+
i += 1;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
254
260
|
if (token === '--distribution-yes') {
|
|
255
261
|
options.distributionYes = parseDistributionUnits(
|
|
256
262
|
requireFlagValue(args, i, '--distribution-yes'),
|
|
@@ -133,6 +133,7 @@ function createParseMirrorGoFlags(deps) {
|
|
|
133
133
|
distributionNoPct: null,
|
|
134
134
|
sources: [],
|
|
135
135
|
sourcesProvided: false,
|
|
136
|
+
validationTicket: null,
|
|
136
137
|
manifestFile: null,
|
|
137
138
|
trustDeploy: false,
|
|
138
139
|
forceGate: false,
|
|
@@ -373,6 +374,11 @@ function createParseMirrorGoFlags(deps) {
|
|
|
373
374
|
i = j - 1;
|
|
374
375
|
continue;
|
|
375
376
|
}
|
|
377
|
+
if (token === '--validation-ticket') {
|
|
378
|
+
options.validationTicket = requireFlagValue(args, i, '--validation-ticket');
|
|
379
|
+
i += 1;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
376
382
|
if (token === '--manifest-file') {
|
|
377
383
|
options.manifestFile = requireFlagValue(args, i, '--manifest-file');
|
|
378
384
|
i += 1;
|