@clawlabz/clawarena-cli 0.3.0
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 +46 -0
- package/bin/arena-onboard.mjs +110 -0
- package/bin/arena-runner.mjs +979 -0
- package/bin/arena-worker.mjs +1038 -0
- package/lib/action-parser.mjs +63 -0
- package/lib/fallback-strategy.mjs +59 -0
- package/package.json +23 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Parse action JSON from agent/LLM responses
|
|
2
|
+
// Handles: raw JSON, markdown code blocks, embedded JSON objects
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse an action from text input (e.g. from stdin or LLM output).
|
|
6
|
+
* Tries multiple extraction strategies for robustness.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} text - Raw text that should contain an action JSON
|
|
9
|
+
* @param {Array<{action: string}>} availableActions - Valid actions for validation
|
|
10
|
+
* @returns {object|null} Parsed action object, or null if unparseable
|
|
11
|
+
*/
|
|
12
|
+
export function parseAction(text, availableActions) {
|
|
13
|
+
if (!text || typeof text !== 'string') return null
|
|
14
|
+
|
|
15
|
+
const trimmed = text.trim()
|
|
16
|
+
if (!trimmed) return null
|
|
17
|
+
|
|
18
|
+
// Strategy 1: Direct JSON.parse
|
|
19
|
+
const direct = tryParse(trimmed)
|
|
20
|
+
if (direct && isValidAction(direct, availableActions)) return direct
|
|
21
|
+
|
|
22
|
+
// Strategy 2: Extract from markdown ```json ... ``` blocks
|
|
23
|
+
const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)```/)
|
|
24
|
+
if (codeBlockMatch) {
|
|
25
|
+
const parsed = tryParse(codeBlockMatch[1].trim())
|
|
26
|
+
if (parsed && isValidAction(parsed, availableActions)) return parsed
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Strategy 3: Find first { ... } in the text
|
|
30
|
+
const braceMatch = trimmed.match(/\{[^{}]*\}/)
|
|
31
|
+
if (braceMatch) {
|
|
32
|
+
const parsed = tryParse(braceMatch[0])
|
|
33
|
+
if (parsed && isValidAction(parsed, availableActions)) return parsed
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Strategy 4: Find nested { ... { ... } ... }
|
|
37
|
+
const nestedMatch = trimmed.match(/\{[^}]*\{[^}]*\}[^}]*\}/)
|
|
38
|
+
if (nestedMatch) {
|
|
39
|
+
const parsed = tryParse(nestedMatch[0])
|
|
40
|
+
if (parsed && isValidAction(parsed, availableActions)) return parsed
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function tryParse(text) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(text)
|
|
49
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && typeof parsed.action === 'string') {
|
|
50
|
+
return parsed
|
|
51
|
+
}
|
|
52
|
+
return null
|
|
53
|
+
} catch {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isValidAction(parsed, availableActions) {
|
|
59
|
+
if (!Array.isArray(availableActions) || availableActions.length === 0) {
|
|
60
|
+
return typeof parsed.action === 'string' && parsed.action.length > 0
|
|
61
|
+
}
|
|
62
|
+
return availableActions.some(a => a.action === parsed.action)
|
|
63
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Fallback strategy: pick a random valid action from availableActions
|
|
2
|
+
// Used when no LLM / agent-mode decision is available
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pick a random valid action from the available actions list.
|
|
6
|
+
* Prefers actions marked as isFallback=true.
|
|
7
|
+
* For params: default → use it, enum → random value, number → min value,
|
|
8
|
+
* string with validTargets → random target, string without → '(auto)'.
|
|
9
|
+
*
|
|
10
|
+
* @param {Array<{action: string, params?: Record<string, object>, isFallback?: boolean}>} availableActions
|
|
11
|
+
* @returns {object|null} An action object ready to submit, or null if no actions available
|
|
12
|
+
*/
|
|
13
|
+
export function pickFallbackAction(availableActions) {
|
|
14
|
+
if (!Array.isArray(availableActions) || availableActions.length === 0) return null
|
|
15
|
+
|
|
16
|
+
// Prefer fallback-marked actions
|
|
17
|
+
const fallbacks = availableActions.filter(a => a.isFallback)
|
|
18
|
+
const candidates = fallbacks.length > 0 ? fallbacks : availableActions
|
|
19
|
+
|
|
20
|
+
const chosen = candidates[Math.floor(Math.random() * candidates.length)]
|
|
21
|
+
const action = { action: chosen.action }
|
|
22
|
+
|
|
23
|
+
if (chosen.params) {
|
|
24
|
+
for (const [key, constraint] of Object.entries(chosen.params)) {
|
|
25
|
+
action[key] = resolveParam(constraint)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return action
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveParam(constraint) {
|
|
33
|
+
if (!constraint || typeof constraint !== 'object') return null
|
|
34
|
+
|
|
35
|
+
// If a default is explicitly provided, prefer it
|
|
36
|
+
if (constraint.default !== undefined && constraint.default !== null) {
|
|
37
|
+
return constraint.default
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
switch (constraint.type) {
|
|
41
|
+
case 'enum': {
|
|
42
|
+
const values = Array.isArray(constraint.values) ? constraint.values : []
|
|
43
|
+
return values.length > 0 ? values[Math.floor(Math.random() * values.length)] : null
|
|
44
|
+
}
|
|
45
|
+
case 'number': {
|
|
46
|
+
const min = typeof constraint.min === 'number' ? constraint.min : 0
|
|
47
|
+
// Use min value as safest default
|
|
48
|
+
return min
|
|
49
|
+
}
|
|
50
|
+
case 'string': {
|
|
51
|
+
const targets = Array.isArray(constraint.validTargets) ? constraint.validTargets : []
|
|
52
|
+
return targets.length > 0 ? targets[Math.floor(Math.random() * targets.length)] : '(auto)'
|
|
53
|
+
}
|
|
54
|
+
case 'boolean':
|
|
55
|
+
return false
|
|
56
|
+
default:
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clawlabz/clawarena-cli",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "ClawArena CLI for AI agents (Claude Code / Codex CLI / Gemini CLI)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"bin": {
|
|
9
|
+
"clawarena-cli": "./bin/arena-runner.mjs",
|
|
10
|
+
"clawarena": "./bin/arena-runner.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"lib"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "echo 'no build step'",
|
|
21
|
+
"lint": "node -e \"process.exit(0)\""
|
|
22
|
+
}
|
|
23
|
+
}
|