@kroxy/kroxy 1.0.16 → 1.0.18
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/dist/index.js +14 -1
- package/dist/src/client.js +3 -2
- package/dist/src/tools/autoagent.js +69 -18
- package/dist/src/tools/hire.js +19 -11
- package/dist/src/tools/offer.js +9 -2
- package/dist/src/tools/setup.js +10 -6
- package/index.ts +14 -0
- package/openclaw.plugin.json +19 -3
- package/package.json +9 -3
- package/scripts/setup-openclaw.mjs +138 -0
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { hireParams, executeHire } from './src/tools/hire.js';
|
|
2
3
|
import { offerParams, executeOffer } from './src/tools/offer.js';
|
|
3
4
|
import { reputationParams, executeReputation } from './src/tools/reputation.js';
|
|
@@ -8,6 +9,11 @@ import { browseParams, executeBrowse } from './src/tools/browse.js';
|
|
|
8
9
|
import { disputeParams, executeDispute } from './src/tools/dispute.js';
|
|
9
10
|
import { historyParams, executeHistory } from './src/tools/history.js';
|
|
10
11
|
import { autoagentParams, executeAutoagent } from './src/tools/autoagent.js';
|
|
12
|
+
const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
|
|
13
|
+
function demoDerivedAddress() {
|
|
14
|
+
const hash = createHash('sha256').update('kroxy-demo-wallet').digest('hex');
|
|
15
|
+
return `0x${hash.slice(0, 40)}`;
|
|
16
|
+
}
|
|
11
17
|
export default {
|
|
12
18
|
id: 'kroxy',
|
|
13
19
|
name: 'Kroxy',
|
|
@@ -25,6 +31,13 @@ export default {
|
|
|
25
31
|
process.env.KROXY_DEMO_MODE = cfg.KROXY_DEMO_MODE;
|
|
26
32
|
if (cfg.NEXUS_URL)
|
|
27
33
|
process.env.NEXUS_URL = cfg.NEXUS_URL;
|
|
34
|
+
if (!process.env.KROXY_API_URL)
|
|
35
|
+
process.env.KROXY_API_URL = DEFAULT_API_URL;
|
|
36
|
+
if (!process.env.KROXY_DEMO_MODE && !process.env.KROXY_AGENT_PRIVATE_KEY)
|
|
37
|
+
process.env.KROXY_DEMO_MODE = '1';
|
|
38
|
+
if (process.env.KROXY_DEMO_MODE === '1' && !process.env.KROXY_AGENT_WALLET) {
|
|
39
|
+
process.env.KROXY_AGENT_WALLET = demoDerivedAddress();
|
|
40
|
+
}
|
|
28
41
|
// ── Setup / Onboarding ────────────────────────────────────────────────────
|
|
29
42
|
// Run this first to check configuration and get guided setup instructions.
|
|
30
43
|
api.registerTool({
|
|
@@ -139,4 +152,4 @@ export default {
|
|
|
139
152
|
}, { optional: true });
|
|
140
153
|
},
|
|
141
154
|
};
|
|
142
|
-
//# sourceMappingURL=index.js.map
|
|
155
|
+
//# sourceMappingURL=index.js.map
|
package/dist/src/client.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* so runtime-injected env vars are always used.
|
|
5
5
|
*/
|
|
6
6
|
import { fetchWithRetry } from './utils/fetchWithRetry.js';
|
|
7
|
+
const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
|
|
7
8
|
function apiBase() {
|
|
8
|
-
return process.env.KROXY_API_URL ??
|
|
9
|
+
return process.env.KROXY_API_URL ?? DEFAULT_API_URL;
|
|
9
10
|
}
|
|
10
11
|
function authHeaders() {
|
|
11
12
|
const key = process.env.KROXY_API_KEY ?? '';
|
|
@@ -149,4 +150,4 @@ export async function pingApi() {
|
|
|
149
150
|
return false;
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
|
-
//# sourceMappingURL=client.js.map
|
|
153
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* kroxy_autoagent — Autonomous multi-step orchestrator.
|
|
3
3
|
*
|
|
4
4
|
* Takes a natural-language goal, decomposes it into 2–4 subtasks via Claude
|
|
5
|
-
* (or a rule-based fallback if
|
|
6
|
-
* for
|
|
5
|
+
* through Lava's gateway (or a rule-based fallback if keys are unset), fires
|
|
6
|
+
* kroxy_hire for all subtasks in parallel, then synthesizes outputs into a
|
|
7
|
+
* unified deliverable via Lava.
|
|
7
8
|
*/
|
|
8
9
|
import { Type } from '@sinclair/typebox';
|
|
9
10
|
import { executeHire } from './hire.js';
|
|
@@ -20,8 +21,9 @@ export const autoagentParams = Type.Object({
|
|
|
20
21
|
});
|
|
21
22
|
// ─── Decomposition ────────────────────────────────────────────────────────────
|
|
22
23
|
/**
|
|
23
|
-
* Ask Claude to decompose the goal into 2–4 subtasks.
|
|
24
|
-
* Falls back to
|
|
24
|
+
* Ask Claude (via Lava) to decompose the goal into 2–4 subtasks.
|
|
25
|
+
* Falls back to keyword-based decomposer if LAVA_SECRET_KEY or
|
|
26
|
+
* ANTHROPIC_API_KEY are not set.
|
|
25
27
|
*/
|
|
26
28
|
async function decomposeGoal(goal) {
|
|
27
29
|
const hasLava = process.env.LAVA_SECRET_KEY && process.env.ANTHROPIC_API_KEY;
|
|
@@ -39,13 +41,13 @@ async function decomposeGoal(goal) {
|
|
|
39
41
|
});
|
|
40
42
|
const parsed = JSON.parse(text.trim());
|
|
41
43
|
if (Array.isArray(parsed) && parsed.length > 0)
|
|
42
|
-
return parsed.slice(0, 4);
|
|
44
|
+
return { subtasks: parsed.slice(0, 4), viaLava: true };
|
|
43
45
|
}
|
|
44
46
|
catch {
|
|
45
47
|
// Fall through to rule-based
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
|
-
return ruleBasedDecompose(goal);
|
|
50
|
+
return { subtasks: ruleBasedDecompose(goal), viaLava: false };
|
|
49
51
|
}
|
|
50
52
|
/** Keyword-based decomposer — works without any API key. */
|
|
51
53
|
function ruleBasedDecompose(goal) {
|
|
@@ -70,30 +72,73 @@ function ruleBasedDecompose(goal) {
|
|
|
70
72
|
}
|
|
71
73
|
return subtasks.slice(0, 4);
|
|
72
74
|
}
|
|
75
|
+
// ─── Synthesis ────────────────────────────────────────────────────────────────
|
|
76
|
+
/**
|
|
77
|
+
* Use Lava/Claude to merge individual subtask deliverables into a single
|
|
78
|
+
* coherent summary. Best-effort — silently skipped if Lava is unavailable.
|
|
79
|
+
*/
|
|
80
|
+
async function synthesizeDeliverables(goal, receipts) {
|
|
81
|
+
const hasLava = process.env.LAVA_SECRET_KEY && process.env.ANTHROPIC_API_KEY;
|
|
82
|
+
if (!hasLava || receipts.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
const snippets = receipts
|
|
85
|
+
.filter(d => d?.deliverable?.summary)
|
|
86
|
+
.map((d, i) => `[Subtask ${i + 1}]\n${d.deliverable.summary}`)
|
|
87
|
+
.join('\n\n');
|
|
88
|
+
if (!snippets)
|
|
89
|
+
return null;
|
|
90
|
+
try {
|
|
91
|
+
return await lavaAnthropic({
|
|
92
|
+
model: 'claude-haiku-4-5-20251001',
|
|
93
|
+
max_tokens: 512,
|
|
94
|
+
system: 'You are a synthesis assistant. Given outputs from multiple AI agent subtasks, ' +
|
|
95
|
+
'write a concise unified summary (3–5 sentences) that integrates all key findings ' +
|
|
96
|
+
'into a coherent deliverable for the original goal.',
|
|
97
|
+
messages: [{ role: 'user', content: `Original goal: ${goal}\n\nSubtask outputs:\n${snippets}` }],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
73
104
|
// ─── Orchestrator ─────────────────────────────────────────────────────────────
|
|
74
105
|
export async function executeAutoagent(params) {
|
|
75
106
|
const goal = params.goal;
|
|
76
107
|
const maxBudget = params.maxBudget ?? 20;
|
|
77
108
|
// 1. Decompose
|
|
78
|
-
const subtasks = await decomposeGoal(goal);
|
|
109
|
+
const { subtasks, viaLava } = await decomposeGoal(goal);
|
|
79
110
|
const pricePerTask = parseFloat((maxBudget / subtasks.length).toFixed(2));
|
|
111
|
+
const modeLabel = viaLava
|
|
112
|
+
? 'LLM decomposition via Lava'
|
|
113
|
+
: '⚠ Keyword fallback — set LAVA_SECRET_KEY + ANTHROPIC_API_KEY for intelligent decomposition';
|
|
80
114
|
const lines = [
|
|
81
115
|
`AutoAgent: ${goal.slice(0, 80)}`,
|
|
82
116
|
`${'─'.repeat(50)}`,
|
|
83
117
|
`Budget: $${maxBudget} USDC across ${subtasks.length} subtask${subtasks.length === 1 ? '' : 's'} ($${pricePerTask} each)`,
|
|
118
|
+
`Mode: ${modeLabel}`,
|
|
84
119
|
'',
|
|
85
120
|
];
|
|
121
|
+
// 2. List all subtasks upfront before firing
|
|
122
|
+
subtasks.forEach(({ task, capability }, i) => {
|
|
123
|
+
lines.push(`[${i + 1}/${subtasks.length}] ${capability.toUpperCase()}: ${task.slice(0, 70)}`);
|
|
124
|
+
});
|
|
125
|
+
lines.push('', `Firing all ${subtasks.length} subtask${subtasks.length === 1 ? '' : 's'} in parallel…`, '');
|
|
126
|
+
// 3. Fire all hires concurrently
|
|
127
|
+
const settled = await Promise.allSettled(
|
|
128
|
+
subtasks.map(({ task, capability }) =>
|
|
129
|
+
executeHire({ task, maxPrice: pricePerTask, capability })
|
|
130
|
+
)
|
|
131
|
+
);
|
|
86
132
|
const receipts = [];
|
|
87
133
|
let totalSpent = 0;
|
|
88
|
-
|
|
89
|
-
|
|
134
|
+
for (let i = 0; i < settled.length; i++) {
|
|
135
|
+
const result = settled[i];
|
|
90
136
|
const { task, capability } = subtasks[i];
|
|
91
|
-
lines.push(`[${i + 1}
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
totalSpent += paid;
|
|
137
|
+
lines.push(`[${i + 1}] ${capability.toUpperCase()}: ${task.slice(0, 60)}`);
|
|
138
|
+
if (result.status === 'fulfilled') {
|
|
139
|
+
const d = result.value.details;
|
|
140
|
+
const paid = parseFloat(String(d?.amountPaid ?? '0'));
|
|
141
|
+
totalSpent += isNaN(paid) ? 0 : paid;
|
|
97
142
|
lines.push(` ✓ Completed in ${d?.duration ?? '?'} — paid $${d?.amountPaid ?? '?'}`);
|
|
98
143
|
lines.push(` Escrow: ${d?.escrowId ?? '?'}`);
|
|
99
144
|
if (d?.deliverable?.summary) {
|
|
@@ -102,15 +147,20 @@ export async function executeAutoagent(params) {
|
|
|
102
147
|
lines.push('');
|
|
103
148
|
receipts.push(d);
|
|
104
149
|
}
|
|
105
|
-
|
|
106
|
-
const msg =
|
|
150
|
+
else {
|
|
151
|
+
const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
107
152
|
lines.push(` ✗ Failed: ${msg.slice(0, 120)}`);
|
|
108
153
|
lines.push('');
|
|
109
154
|
}
|
|
110
155
|
}
|
|
156
|
+
// 4. Synthesize outputs into a unified deliverable (via Lava — best-effort)
|
|
157
|
+
const synthesis = await synthesizeDeliverables(goal, receipts);
|
|
111
158
|
lines.push(`${'─'.repeat(50)}`);
|
|
112
159
|
lines.push(`Total spent: $${totalSpent.toFixed(2)} USDC / $${maxBudget} budget`);
|
|
113
160
|
lines.push(`Subtasks completed: ${receipts.length}/${subtasks.length}`);
|
|
161
|
+
if (synthesis) {
|
|
162
|
+
lines.push('', '── Unified Deliverable ──', synthesis);
|
|
163
|
+
}
|
|
114
164
|
return {
|
|
115
165
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
116
166
|
details: {
|
|
@@ -119,7 +169,8 @@ export async function executeAutoagent(params) {
|
|
|
119
169
|
receipts,
|
|
120
170
|
totalSpent: totalSpent.toFixed(2),
|
|
121
171
|
budgetUsed: `$${totalSpent.toFixed(2)} / $${maxBudget}`,
|
|
172
|
+
synthesis: synthesis ?? undefined,
|
|
122
173
|
},
|
|
123
174
|
};
|
|
124
175
|
}
|
|
125
|
-
//# sourceMappingURL=autoagent.js.map
|
|
176
|
+
//# sourceMappingURL=autoagent.js.map
|
package/dist/src/tools/hire.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { Type } from '@sinclair/typebox';
|
|
3
3
|
import { findAgents, postJob, acceptBid, pollJob, } from '../client.js';
|
|
4
|
+
const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
|
|
4
5
|
export const hireParams = Type.Object({
|
|
5
6
|
task: Type.String({ description: 'What you want the hired agent to do, e.g. "Research the top AI payment startups"' }),
|
|
6
7
|
maxPrice: Type.Optional(Type.Number({ minimum: 0.01, description: 'Maximum USDC budget, default 5.00' })),
|
|
7
|
-
capability: Type.Optional(Type.String({ description: 'Agent capability to match: research, writing, coding. Auto-detected from task if omitted.' })),
|
|
8
|
+
capability: Type.Optional(Type.String({ description: 'Agent capability to match: research, writing, coding, planning. Auto-detected from task if omitted.' })),
|
|
8
9
|
minRep: Type.Optional(Type.Number({ minimum: 0, maximum: 100, description: 'Minimum reputation score (0–100) required. Default: 0 (any agent accepted).' })),
|
|
9
10
|
});
|
|
10
11
|
function detectCapability(task) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (/write|draft|essay|blog|article|copy/i.test(task))
|
|
14
|
-
return 'writing';
|
|
15
|
-
if (/code|script|function|implement|build|program/i.test(task))
|
|
12
|
+
// Check coding first — "build" would otherwise fall through to planning
|
|
13
|
+
if (/code|script|function|implement|develop|engineer|program|smart contract/i.test(task))
|
|
16
14
|
return 'coding';
|
|
15
|
+
if (/write|draft|essay|blog|article|copy|content|landing page|marketing|email/i.test(task))
|
|
16
|
+
return 'writing';
|
|
17
|
+
if (/plan|roadmap|strateg|architect|design|outline|milestone|go-to-market/i.test(task))
|
|
18
|
+
return 'planning';
|
|
19
|
+
if (/research|find|search|analyz|summariz|look up|investigat|gather|survey/i.test(task))
|
|
20
|
+
return 'research';
|
|
17
21
|
return 'research';
|
|
18
22
|
}
|
|
19
23
|
function buildConditions(nexusUrl, jobId) {
|
|
@@ -49,6 +53,10 @@ function formatDuration(startMs) {
|
|
|
49
53
|
const mins = Math.floor(secs / 60);
|
|
50
54
|
return `${mins}m ${secs % 60}s`;
|
|
51
55
|
}
|
|
56
|
+
function demoDerivedAddress() {
|
|
57
|
+
const hash = createHash('sha256').update('kroxy-demo-wallet').digest('hex');
|
|
58
|
+
return `0x${hash.slice(0, 40)}`;
|
|
59
|
+
}
|
|
52
60
|
function buildReceipt(opts) {
|
|
53
61
|
const lines = [
|
|
54
62
|
`✅ Job Complete — ${opts.task.slice(0, 60)}${opts.task.length > 60 ? '…' : ''}`,
|
|
@@ -70,10 +78,10 @@ function buildReceipt(opts) {
|
|
|
70
78
|
return lines.join('\n');
|
|
71
79
|
}
|
|
72
80
|
export async function executeHire(params) {
|
|
73
|
-
const wallet = process.env.KROXY_AGENT_WALLET;
|
|
74
81
|
const privateKey = process.env.KROXY_AGENT_PRIVATE_KEY;
|
|
75
|
-
const demoMode = process.env.KROXY_DEMO_MODE === '1';
|
|
76
|
-
const
|
|
82
|
+
const demoMode = process.env.KROXY_DEMO_MODE === '1' || (!process.env.KROXY_DEMO_MODE && !privateKey);
|
|
83
|
+
const wallet = process.env.KROXY_AGENT_WALLET ?? (demoMode ? demoDerivedAddress() : undefined);
|
|
84
|
+
const apiBase = process.env.KROXY_API_URL ?? DEFAULT_API_URL;
|
|
77
85
|
// In demo mode, point at the embedded demo agent in the API if NEXUS_URL not explicitly set
|
|
78
86
|
const nexusUrl = process.env.NEXUS_URL ?? (demoMode ? `${apiBase}/demo-agent` : 'http://localhost:3003');
|
|
79
87
|
if (!wallet)
|
|
@@ -160,4 +168,4 @@ export async function executeHire(params) {
|
|
|
160
168
|
details,
|
|
161
169
|
};
|
|
162
170
|
}
|
|
163
|
-
//# sourceMappingURL=hire.js.map
|
|
171
|
+
//# sourceMappingURL=hire.js.map
|
package/dist/src/tools/offer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { registerProvider } from '../client.js';
|
|
3
4
|
export const offerParams = Type.Object({
|
|
4
5
|
capability: Type.String({ description: 'Capability to offer: research, writing, coding, analysis, etc.' }),
|
|
@@ -6,8 +7,14 @@ export const offerParams = Type.Object({
|
|
|
6
7
|
endpoint: Type.String({ description: 'Public URL where your agent receives job webhooks, e.g. https://myagent.example.com' }),
|
|
7
8
|
name: Type.Optional(Type.String({ description: 'Display name for your agent on the Kroxy job board' })),
|
|
8
9
|
});
|
|
10
|
+
function demoDerivedAddress() {
|
|
11
|
+
const hash = createHash('sha256').update('kroxy-demo-wallet').digest('hex');
|
|
12
|
+
return `0x${hash.slice(0, 40)}`;
|
|
13
|
+
}
|
|
9
14
|
export async function executeOffer(params) {
|
|
10
|
-
const
|
|
15
|
+
const privateKey = process.env.KROXY_AGENT_PRIVATE_KEY;
|
|
16
|
+
const demoMode = process.env.KROXY_DEMO_MODE === '1' || (!process.env.KROXY_DEMO_MODE && !privateKey);
|
|
17
|
+
const wallet = process.env.KROXY_AGENT_WALLET ?? (demoMode ? demoDerivedAddress() : undefined);
|
|
11
18
|
if (!wallet)
|
|
12
19
|
throw new Error('KROXY_AGENT_WALLET is not configured');
|
|
13
20
|
const agent = await registerProvider({
|
|
@@ -33,4 +40,4 @@ export async function executeOffer(params) {
|
|
|
33
40
|
details: result,
|
|
34
41
|
};
|
|
35
42
|
}
|
|
36
|
-
//# sourceMappingURL=offer.js.map
|
|
43
|
+
//# sourceMappingURL=offer.js.map
|
package/dist/src/tools/setup.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Type } from '@sinclair/typebox';
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { pingApi } from '../client.js';
|
|
4
4
|
export const setupParams = Type.Object({});
|
|
5
|
+
const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
|
|
5
6
|
function icon(s) {
|
|
6
7
|
return s === 'ok' ? '✅' : s === 'warn' ? '⚠️ ' : '❌';
|
|
7
8
|
}
|
|
@@ -19,11 +20,11 @@ function demoDerivedAddress() {
|
|
|
19
20
|
return `0x${hash.slice(0, 40)}`;
|
|
20
21
|
}
|
|
21
22
|
export async function executeSetup(_params) {
|
|
22
|
-
const apiUrl = process.env.KROXY_API_URL ??
|
|
23
|
+
const apiUrl = process.env.KROXY_API_URL ?? DEFAULT_API_URL;
|
|
23
24
|
const apiKey = process.env.KROXY_API_KEY;
|
|
24
25
|
const wallet = process.env.KROXY_AGENT_WALLET;
|
|
25
26
|
const privateKey = process.env.KROXY_AGENT_PRIVATE_KEY;
|
|
26
|
-
const demoMode = process.env.KROXY_DEMO_MODE === '1';
|
|
27
|
+
const demoMode = process.env.KROXY_DEMO_MODE === '1' || (!process.env.KROXY_DEMO_MODE && !privateKey);
|
|
27
28
|
const nexusUrl = process.env.NEXUS_URL;
|
|
28
29
|
const checks = [];
|
|
29
30
|
let blockingIssues = 0;
|
|
@@ -55,10 +56,13 @@ export async function executeSetup(_params) {
|
|
|
55
56
|
else {
|
|
56
57
|
checks.push({
|
|
57
58
|
item: 'API key (KROXY_API_KEY)',
|
|
58
|
-
status: 'error',
|
|
59
|
-
hint:
|
|
59
|
+
status: demoMode ? 'warn' : 'error',
|
|
60
|
+
hint: demoMode
|
|
61
|
+
? 'Optional in demo mode. Add an API key for higher rate limits and live usage.'
|
|
62
|
+
: 'Required for posting jobs and hiring agents. Set in plugin config.',
|
|
60
63
|
});
|
|
61
|
-
|
|
64
|
+
if (!demoMode)
|
|
65
|
+
blockingIssues++;
|
|
62
66
|
}
|
|
63
67
|
// 4. Agent wallet
|
|
64
68
|
let suggestedWallet;
|
|
@@ -138,4 +142,4 @@ export async function executeSetup(_params) {
|
|
|
138
142
|
details: result,
|
|
139
143
|
};
|
|
140
144
|
}
|
|
141
|
-
//# sourceMappingURL=setup.js.map
|
|
145
|
+
//# sourceMappingURL=setup.js.map
|
package/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { hireParams, executeHire } from './src/tools/hire.js';
|
|
3
4
|
import { offerParams, executeOffer } from './src/tools/offer.js';
|
|
4
5
|
import { reputationParams, executeReputation } from './src/tools/reputation.js';
|
|
@@ -21,6 +22,13 @@ type DisputeToolParams = Parameters<typeof executeDispute>[0];
|
|
|
21
22
|
type HistoryToolParams = Parameters<typeof executeHistory>[0];
|
|
22
23
|
type AutoagentToolParams = Parameters<typeof executeAutoagent>[0];
|
|
23
24
|
|
|
25
|
+
const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
|
|
26
|
+
|
|
27
|
+
function demoDerivedAddress(): string {
|
|
28
|
+
const hash = createHash('sha256').update('kroxy-demo-wallet').digest('hex');
|
|
29
|
+
return `0x${hash.slice(0, 40)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
export default {
|
|
25
33
|
id: 'kroxy',
|
|
26
34
|
name: 'Kroxy',
|
|
@@ -33,6 +41,12 @@ export default {
|
|
|
33
41
|
if (cfg.KROXY_DEMO_MODE) process.env.KROXY_DEMO_MODE = cfg.KROXY_DEMO_MODE;
|
|
34
42
|
if (cfg.NEXUS_URL) process.env.NEXUS_URL = cfg.NEXUS_URL;
|
|
35
43
|
|
|
44
|
+
if (!process.env.KROXY_API_URL) process.env.KROXY_API_URL = DEFAULT_API_URL;
|
|
45
|
+
if (!process.env.KROXY_DEMO_MODE && !process.env.KROXY_AGENT_PRIVATE_KEY) process.env.KROXY_DEMO_MODE = '1';
|
|
46
|
+
if (process.env.KROXY_DEMO_MODE === '1' && !process.env.KROXY_AGENT_WALLET) {
|
|
47
|
+
process.env.KROXY_AGENT_WALLET = demoDerivedAddress();
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
// ── Setup / Onboarding ────────────────────────────────────────────────────
|
|
37
51
|
// Run this first to check configuration and get guided setup instructions.
|
|
38
52
|
api.registerTool({
|
package/openclaw.plugin.json
CHANGED
|
@@ -8,12 +8,20 @@
|
|
|
8
8
|
"type": "object",
|
|
9
9
|
"additionalProperties": false,
|
|
10
10
|
"properties": {
|
|
11
|
-
"KROXY_API_URL": {
|
|
11
|
+
"KROXY_API_URL": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "https://api-production-1b45.up.railway.app"
|
|
14
|
+
},
|
|
12
15
|
"KROXY_API_KEY": { "type": "string" },
|
|
13
16
|
"KROXY_AGENT_WALLET": { "type": "string" },
|
|
14
17
|
"KROXY_AGENT_PRIVATE_KEY": { "type": "string" },
|
|
15
18
|
"NEXUS_URL": { "type": "string" },
|
|
16
|
-
"
|
|
19
|
+
"LAVA_SECRET_KEY": { "type": "string" },
|
|
20
|
+
"ANTHROPIC_API_KEY": { "type": "string" },
|
|
21
|
+
"KROXY_DEMO_MODE": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"default": "1"
|
|
24
|
+
}
|
|
17
25
|
},
|
|
18
26
|
"required": []
|
|
19
27
|
},
|
|
@@ -25,6 +33,14 @@
|
|
|
25
33
|
"KROXY_AGENT_PRIVATE_KEY": {
|
|
26
34
|
"label": "Kroxy Private Key",
|
|
27
35
|
"sensitive": true
|
|
36
|
+
},
|
|
37
|
+
"LAVA_SECRET_KEY": {
|
|
38
|
+
"label": "Lava Secret Key (enables intelligent decomposition)",
|
|
39
|
+
"sensitive": true
|
|
40
|
+
},
|
|
41
|
+
"ANTHROPIC_API_KEY": {
|
|
42
|
+
"label": "Anthropic API Key (required with Lava)",
|
|
43
|
+
"sensitive": true
|
|
28
44
|
}
|
|
29
45
|
}
|
|
30
|
-
}
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kroxy/kroxy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Trustless agent-to-agent payments via USDC escrow on Base blockchain",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
19
|
"index.ts",
|
|
20
|
-
"openclaw.plugin.json"
|
|
20
|
+
"openclaw.plugin.json",
|
|
21
|
+
"scripts"
|
|
21
22
|
],
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"@sinclair/typebox": "^0.32.0"
|
|
@@ -29,8 +30,13 @@
|
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"openclaw": "*"
|
|
31
32
|
},
|
|
33
|
+
"bin": {
|
|
34
|
+
"kroxy-setup": "./scripts/setup-openclaw.mjs"
|
|
35
|
+
},
|
|
32
36
|
"scripts": {
|
|
33
37
|
"build": "tsc",
|
|
34
|
-
"dev": "tsc --watch"
|
|
38
|
+
"dev": "tsc --watch",
|
|
39
|
+
"postinstall": "node ./scripts/setup-openclaw.mjs --postinstall",
|
|
40
|
+
"setup:openclaw": "node ./scripts/setup-openclaw.mjs"
|
|
35
41
|
}
|
|
36
42
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import process from 'node:process';
|
|
8
|
+
import readline from 'node:readline/promises';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
|
|
11
|
+
|
|
12
|
+
function demoDerivedAddress() {
|
|
13
|
+
const hash = createHash('sha256').update('kroxy-demo-wallet').digest('hex');
|
|
14
|
+
return `0x${hash.slice(0, 40)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveConfigPath() {
|
|
18
|
+
const explicit = process.env.OPENCLAW_CONFIG_PATH?.trim();
|
|
19
|
+
if (explicit) return explicit;
|
|
20
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() || path.join(os.homedir(), '.openclaw');
|
|
21
|
+
return path.join(stateDir, 'openclaw.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureObject(parent, key) {
|
|
25
|
+
const current = parent[key];
|
|
26
|
+
if (!current || typeof current !== 'object' || Array.isArray(current)) {
|
|
27
|
+
parent[key] = {};
|
|
28
|
+
}
|
|
29
|
+
return parent[key];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function askYesNo(rl, question, defaultYes = true) {
|
|
33
|
+
const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
|
|
34
|
+
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
35
|
+
if (!answer) return defaultYes;
|
|
36
|
+
if (['y', 'yes'].includes(answer)) return true;
|
|
37
|
+
if (['n', 'no'].includes(answer)) return false;
|
|
38
|
+
return defaultYes;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseJsonConfig(raw, configPath) {
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
45
|
+
throw new Error('top-level JSON value must be an object');
|
|
46
|
+
}
|
|
47
|
+
return parsed;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Could not parse ${configPath} as strict JSON (${String(err)}).\n` +
|
|
51
|
+
'Run: openclaw config set plugins.entries.kroxy.config.KROXY_API_URL ' +
|
|
52
|
+
`"${DEFAULT_API_URL}"`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function runSetup({ postinstall }) {
|
|
58
|
+
const configPath = resolveConfigPath();
|
|
59
|
+
const commandHint = 'node ~/.openclaw/extensions/kroxy/scripts/setup-openclaw.mjs';
|
|
60
|
+
|
|
61
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
62
|
+
if (postinstall) {
|
|
63
|
+
console.log(`[kroxy] Skipping interactive setup (non-interactive terminal). Run ${commandHint}`);
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!fs.existsSync(configPath)) {
|
|
69
|
+
console.log(`[kroxy] OpenClaw config not found at ${configPath}.`);
|
|
70
|
+
console.log('[kroxy] Run OpenClaw once, then run this setup wizard again.');
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
75
|
+
const cfg = parseJsonConfig(raw, configPath);
|
|
76
|
+
const plugins = ensureObject(cfg, 'plugins');
|
|
77
|
+
const entries = ensureObject(plugins, 'entries');
|
|
78
|
+
const kroxy = ensureObject(entries, 'kroxy');
|
|
79
|
+
const pluginConfig = ensureObject(kroxy, 'config');
|
|
80
|
+
|
|
81
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
82
|
+
try {
|
|
83
|
+
const start = await askYesNo(rl, 'Configure Kroxy in OpenClaw now?', true);
|
|
84
|
+
if (!start) return 0;
|
|
85
|
+
|
|
86
|
+
if (await askYesNo(rl, `Set KROXY_API_URL to ${DEFAULT_API_URL}?`, !pluginConfig.KROXY_API_URL)) {
|
|
87
|
+
pluginConfig.KROXY_API_URL = DEFAULT_API_URL;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const defaultDemo = pluginConfig.KROXY_DEMO_MODE !== '0';
|
|
91
|
+
if (await askYesNo(rl, 'Enable demo mode by default (recommended for first run)?', defaultDemo)) {
|
|
92
|
+
pluginConfig.KROXY_DEMO_MODE = '1';
|
|
93
|
+
} else if (pluginConfig.KROXY_DEMO_MODE === '1') {
|
|
94
|
+
delete pluginConfig.KROXY_DEMO_MODE;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (pluginConfig.KROXY_DEMO_MODE === '1') {
|
|
98
|
+
const shouldSetDemoWallet = await askYesNo(
|
|
99
|
+
rl,
|
|
100
|
+
'Auto-set a demo wallet address for KROXY_AGENT_WALLET?',
|
|
101
|
+
!pluginConfig.KROXY_AGENT_WALLET
|
|
102
|
+
);
|
|
103
|
+
if (shouldSetDemoWallet) {
|
|
104
|
+
pluginConfig.KROXY_AGENT_WALLET = pluginConfig.KROXY_AGENT_WALLET || demoDerivedAddress();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const setApiKeyNow = await askYesNo(rl, 'Set KROXY_API_KEY now?', false);
|
|
109
|
+
if (setApiKeyNow) {
|
|
110
|
+
const key = (await rl.question('Paste KROXY_API_KEY: ')).trim();
|
|
111
|
+
if (key) pluginConfig.KROXY_API_KEY = key;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const save = await askYesNo(rl, `Save changes to ${configPath}?`, true);
|
|
115
|
+
if (!save) {
|
|
116
|
+
console.log('[kroxy] Setup canceled; no changes saved.');
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fs.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\n`, 'utf8');
|
|
121
|
+
console.log('[kroxy] Saved OpenClaw config for plugin "kroxy".');
|
|
122
|
+
console.log('[kroxy] Restart the gateway to load the new config.');
|
|
123
|
+
return 0;
|
|
124
|
+
} finally {
|
|
125
|
+
rl.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const postinstall = process.argv.includes('--postinstall');
|
|
130
|
+
runSetup({ postinstall }).then(
|
|
131
|
+
(code) => {
|
|
132
|
+
process.exitCode = code;
|
|
133
|
+
},
|
|
134
|
+
(err) => {
|
|
135
|
+
console.error(`[kroxy] Setup wizard failed: ${String(err)}`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
}
|
|
138
|
+
);
|