@kroxy/kroxy 1.0.19 → 1.0.21

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { createHash } from 'node:crypto';
1
+ import { demoDerivedAddress } from './src/utils/wallet.js';
2
2
  import { hireParams, executeHire } from './src/tools/hire.js';
3
3
  import { offerParams, executeOffer } from './src/tools/offer.js';
4
4
  import { reputationParams, executeReputation } from './src/tools/reputation.js';
@@ -10,10 +10,6 @@ import { disputeParams, executeDispute } from './src/tools/dispute.js';
10
10
  import { historyParams, executeHistory } from './src/tools/history.js';
11
11
  import { autoagentParams, executeAutoagent } from './src/tools/autoagent.js';
12
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
- }
17
13
  export default {
18
14
  id: 'kroxy',
19
15
  name: 'Kroxy',
@@ -38,11 +38,11 @@ export interface ConditionsDefinition {
38
38
  requiredPassRate: number;
39
39
  }
40
40
  export interface Condition {
41
- type: 'http_status' | 'json_field' | 'latency_ms' | 'uptime_percent';
41
+ type: 'http_status' | 'json_field' | 'latency_ms' | 'uptime_percent' | 'deliverable_quality';
42
42
  endpoint: string;
43
43
  field?: string;
44
- operator: 'eq' | 'gte' | 'lte' | 'contains';
45
- expected: string | number | boolean;
44
+ operator: 'eq' | 'gte' | 'lte' | 'gt' | 'lt' | 'contains';
45
+ expected: string | number | boolean | Record<string, unknown>;
46
46
  }
47
47
  export interface ReputationResult {
48
48
  walletAddress: string;
@@ -82,7 +82,7 @@ export declare function getAgents(filters?: {
82
82
  limit?: number;
83
83
  }): Promise<AgentProfile[]>;
84
84
  export declare function postJob(jobId: string, description: string, capability: string, budgetUsdc: number, conditionsJson: ConditionsDefinition, posterWallet: string): Promise<Job>;
85
- export declare function acceptBid(jobId: string, bidId: string, payerPrivateKey: string): Promise<{
85
+ export declare function acceptBid(jobId: string, bidId: string): Promise<{
86
86
  jobId: string;
87
87
  bidId: string;
88
88
  escrowId: string;
@@ -108,4 +108,4 @@ export declare function registerProvider(opts: {
108
108
  modelName?: string;
109
109
  }): Promise<AgentProfile>;
110
110
  export declare function pingApi(): Promise<boolean>;
111
- //# sourceMappingURL=client.d.ts.map
111
+ //# sourceMappingURL=client.d.ts.map
@@ -90,8 +90,11 @@ export function postJob(jobId, description, capability, budgetUsdc, conditionsJs
90
90
  conditionsJson,
91
91
  });
92
92
  }
93
- export function acceptBid(jobId, bidId, payerPrivateKey) {
94
- return apiPost(`/api/jobs/${jobId}/accept/${bidId}`, { payerPrivateKey });
93
+ export function acceptBid(jobId, bidId) {
94
+ // Security hardening: private keys are never sent over HTTP request bodies.
95
+ // In live mode, the API must be configured with KROXY_PAYER_PRIVATE_KEY.
96
+ // In demo mode, the API generates a synthetic escrow.
97
+ return apiPost(`/api/jobs/${jobId}/accept/${bidId}`, {});
95
98
  }
96
99
  export function getJob(jobId) {
97
100
  return apiGet(`/api/jobs/${jobId}`);
@@ -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 ANTHROPIC_API_KEY is unset), fires kroxy_hire
6
- * for each subtask sequentially, and returns a unified deliverable.
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
  export declare const autoagentParams: import("@sinclair/typebox").TObject<{
9
10
  goal: import("@sinclair/typebox").TString;
@@ -19,4 +20,4 @@ export declare function executeAutoagent(params: {
19
20
  }];
20
21
  details: unknown;
21
22
  }>;
22
- //# sourceMappingURL=autoagent.d.ts.map
23
+ //# sourceMappingURL=autoagent.d.ts.map
@@ -1,6 +1,7 @@
1
- import { createHash, randomBytes } from 'node:crypto';
1
+ import { randomBytes } from 'node:crypto';
2
2
  import { Type } from '@sinclair/typebox';
3
3
  import { findAgents, postJob, acceptBid, pollJob, } from '../client.js';
4
+ import { demoDerivedAddress } from '../utils/wallet.js';
4
5
  const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
5
6
  export const hireParams = Type.Object({
6
7
  task: Type.String({ description: 'What you want the hired agent to do, e.g. "Research the top AI payment startups"' }),
@@ -21,24 +22,39 @@ function detectCapability(task) {
21
22
  return 'research';
22
23
  }
23
24
  function buildConditions(nexusUrl, jobId) {
25
+ const apiBase = process.env.KROXY_API_URL ?? 'https://api-production-1b45.up.railway.app';
24
26
  return {
25
27
  version: '1.0',
26
28
  escrowId: '',
27
29
  conditions: [
30
+ // Provider liveness: agent endpoint must be healthy
28
31
  { type: 'http_status', endpoint: `${nexusUrl}/health`, operator: 'eq', expected: 200 },
32
+ // Delivery gate: job must reach COMPLETED status before escrow releases
29
33
  {
30
34
  type: 'json_field',
31
- endpoint: `${nexusUrl}/quality-check?jobId=${encodeURIComponent(jobId)}`,
32
- field: 'wordCount',
33
- operator: 'gte',
34
- expected: 100,
35
+ endpoint: `${apiBase}/api/jobs/${encodeURIComponent(jobId)}`,
36
+ field: 'status',
37
+ operator: 'eq',
38
+ expected: 'COMPLETED',
35
39
  },
40
+ // Quality gate: enforce structured, sourced, non-placeholder deliverable.
36
41
  {
37
- type: 'json_field',
38
- endpoint: `${nexusUrl}/quality-check?jobId=${encodeURIComponent(jobId)}`,
39
- field: 'confidence',
42
+ type: 'deliverable_quality',
43
+ endpoint: `${apiBase}/api/jobs/${encodeURIComponent(jobId)}`,
44
+ field: 'deliverable',
40
45
  operator: 'gte',
41
- expected: 0.7,
46
+ expected: {
47
+ minSummaryWords: 120,
48
+ minSummaryChars: 600,
49
+ minSentences: 3,
50
+ minKeyFindings: 3,
51
+ minFindingChars: 20,
52
+ minSources: 2,
53
+ minSourceDomains: 2,
54
+ minLexicalDiversity: 0.45,
55
+ requireCompletedStatus: true,
56
+ forbidPlaceholderPhrases: true,
57
+ },
42
58
  },
43
59
  ],
44
60
  windowSeconds: 120,
@@ -53,10 +69,6 @@ function formatDuration(startMs) {
53
69
  const mins = Math.floor(secs / 60);
54
70
  return `${mins}m ${secs % 60}s`;
55
71
  }
56
- function demoDerivedAddress() {
57
- const hash = createHash('sha256').update('kroxy-demo-wallet').digest('hex');
58
- return `0x${hash.slice(0, 40)}`;
59
- }
60
72
  function buildReceipt(opts) {
61
73
  const lines = [
62
74
  `✅ Job Complete — ${opts.task.slice(0, 60)}${opts.task.length > 60 ? '…' : ''}`,
@@ -77,6 +89,15 @@ function buildReceipt(opts) {
77
89
  }
78
90
  return lines.join('\n');
79
91
  }
92
+ async function waitForBidWithRetries(jobId, waitMs, retries) {
93
+ for (let attempt = 0; attempt <= retries; attempt++) {
94
+ const jobWithBid = await pollJob(jobId, (j) => Boolean(j.bids?.length), waitMs);
95
+ if (jobWithBid?.bids?.length) {
96
+ return { jobWithBid, attempts: attempt + 1 };
97
+ }
98
+ }
99
+ return { jobWithBid: null, attempts: retries + 1 };
100
+ }
80
101
  export async function executeHire(params) {
81
102
  const privateKey = process.env.KROXY_AGENT_PRIVATE_KEY;
82
103
  const demoMode = process.env.KROXY_DEMO_MODE === '1' || (!process.env.KROXY_DEMO_MODE && !privateKey);
@@ -86,13 +107,13 @@ export async function executeHire(params) {
86
107
  const nexusUrl = process.env.NEXUS_URL ?? (demoMode ? `${apiBase}/demo-agent` : 'http://localhost:3003');
87
108
  if (!wallet)
88
109
  throw new Error('KROXY_AGENT_WALLET is not configured. Run kroxy_setup to diagnose.');
89
- if (!demoMode && !privateKey)
90
- throw new Error('KROXY_AGENT_PRIVATE_KEY is not configured. Set KROXY_DEMO_MODE=1 to use demo mode without a real private key.');
91
110
  const task = params.task;
92
111
  const maxPrice = params.maxPrice ?? 5.0;
93
112
  const minRep = params.minRep ?? 0;
94
113
  const capability = params.capability ?? detectCapability(task);
95
- const payerPrivateKey = privateKey ?? 'demo-private-key';
114
+ const bidWaitSeconds = Math.max(30, parseInt(process.env.KROXY_BID_WAIT_SECONDS ?? '60', 10));
115
+ const bidRetries = Math.max(0, parseInt(process.env.KROXY_BID_RETRIES ?? '2', 10));
116
+ const bidWaitMs = bidWaitSeconds * 1000;
96
117
  const jobId = `job_${Date.now()}_${randomBytes(3).toString('hex')}`;
97
118
  const startMs = Date.now();
98
119
  // 1. Find matching agents
@@ -113,10 +134,10 @@ export async function executeHire(params) {
113
134
  // 3. Post job with verifier conditions pointing at Nexus
114
135
  const conditions = buildConditions(nexusUrl, jobId);
115
136
  const job = await postJob(jobId, task, capability, maxPrice, conditions, wallet);
116
- // 4. Wait for a bid (up to 60s)
117
- const jobWithBid = await pollJob(job.id, (j) => Boolean(j.bids?.length), 60_000);
137
+ // 4. Wait for a bid with retries (default: 3 windows × 60s)
138
+ const { jobWithBid, attempts } = await waitForBidWithRetries(job.id, bidWaitMs, bidRetries);
118
139
  if (!jobWithBid?.bids?.length) {
119
- throw new Error(`No bids received within 60 seconds (jobId: ${job.id}). ` +
140
+ throw new Error(`No bids received after ${attempts} attempt(s) of ${bidWaitSeconds}s (jobId: ${job.id}). ` +
120
141
  `The agent may be offline — use kroxy_browse to check availability.`);
121
142
  }
122
143
  const bid = jobWithBid.bids[0];
@@ -127,7 +148,7 @@ export async function executeHire(params) {
127
148
  `Increase maxPrice to $${bidPrice} or higher to accept this bid.`);
128
149
  }
129
150
  // 6. Accept bid — locks USDC in escrow on Base
130
- const escrowResult = await acceptBid(job.id, bid.id, payerPrivateKey);
151
+ const escrowResult = await acceptBid(job.id, bid.id);
131
152
  // 7. Poll for completion (up to 5 minutes)
132
153
  const completed = await pollJob(job.id, (j) => j.status === 'COMPLETED', 300_000);
133
154
  if (!completed) {
@@ -3,12 +3,14 @@ export declare const offerParams: import("@sinclair/typebox").TObject<{
3
3
  price: import("@sinclair/typebox").TNumber;
4
4
  endpoint: import("@sinclair/typebox").TString;
5
5
  name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
6
+ modelName: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
6
7
  }>;
7
8
  export declare function executeOffer(params: {
8
9
  capability: string;
9
10
  price: number;
10
11
  endpoint: string;
11
12
  name?: string;
13
+ modelName?: string;
12
14
  }): Promise<{
13
15
  content: [{
14
16
  type: 'text';
@@ -16,4 +18,4 @@ export declare function executeOffer(params: {
16
18
  }];
17
19
  details: unknown;
18
20
  }>;
19
- //# sourceMappingURL=offer.d.ts.map
21
+ //# sourceMappingURL=offer.d.ts.map
@@ -1,15 +1,29 @@
1
1
  import { Type } from '@sinclair/typebox';
2
- import { createHash } from 'node:crypto';
3
2
  import { registerProvider } from '../client.js';
3
+ import { demoDerivedAddress } from '../utils/wallet.js';
4
4
  export const offerParams = Type.Object({
5
- capability: Type.String({ description: 'Capability to offer: research, writing, coding, analysis, etc.' }),
5
+ capability: Type.String({ description: 'Capability to offer: research, writing, coding, planning, analysis, etc.' }),
6
6
  price: Type.Number({ minimum: 0.01, description: 'Price per job in USDC, e.g. 2.50' }),
7
7
  endpoint: Type.String({ description: 'Public URL where your agent receives job webhooks, e.g. https://myagent.example.com' }),
8
8
  name: Type.Optional(Type.String({ description: 'Display name for your agent on the Kroxy job board' })),
9
+ modelName: Type.Optional(Type.String({ description: 'Model your agent runs, e.g. claude-sonnet-4-6, gpt-4o. Shown on the job board.' })),
9
10
  });
10
- function demoDerivedAddress() {
11
- const hash = createHash('sha256').update('kroxy-demo-wallet').digest('hex');
12
- return `0x${hash.slice(0, 40)}`;
11
+ /** Verify the endpoint is reachable before committing the registration. */
12
+ async function pingEndpoint(url) {
13
+ try {
14
+ const controller = new AbortController();
15
+ const timeout = setTimeout(() => controller.abort(), 5000);
16
+ let resp = await fetch(url, { method: 'HEAD', signal: controller.signal });
17
+ // Some webhook servers reject HEAD even when POST/GET are valid.
18
+ if (resp.status === 405 || resp.status === 501) {
19
+ resp = await fetch(url, { method: 'GET', signal: controller.signal });
20
+ }
21
+ clearTimeout(timeout);
22
+ return resp.status < 500;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
13
27
  }
14
28
  export async function executeOffer(params) {
15
29
  const privateKey = process.env.KROXY_AGENT_PRIVATE_KEY;
@@ -17,26 +31,43 @@ export async function executeOffer(params) {
17
31
  const wallet = process.env.KROXY_AGENT_WALLET ?? (demoMode ? demoDerivedAddress() : undefined);
18
32
  if (!wallet)
19
33
  throw new Error('KROXY_AGENT_WALLET is not configured');
34
+ // Validate endpoint reachability before registering
35
+ const reachable = await pingEndpoint(params.endpoint);
36
+ if (!reachable) {
37
+ throw new Error(`Endpoint not reachable: ${params.endpoint}\nMake sure your agent is running and publicly accessible before registering.`);
38
+ }
20
39
  const agent = await registerProvider({
21
40
  walletAddress: wallet,
22
41
  name: params.name ?? 'MyAgent',
23
42
  endpoint: params.endpoint,
43
+ modelName: params.modelName,
24
44
  capabilities: [params.capability],
25
45
  pricingUsdc: params.price,
26
46
  });
47
+ const lines = [
48
+ `✅ Agent Registered on Kroxy`,
49
+ ``,
50
+ `Wallet: ${agent.walletAddress ?? wallet}`,
51
+ `Name: ${agent.name ?? params.name ?? 'MyAgent'}`,
52
+ `Capability: ${(agent.capabilities ?? [params.capability]).join(', ')}`,
53
+ `Price: $${agent.pricingUsdc ?? params.price} USDC / job`,
54
+ `Endpoint: ${params.endpoint}`,
55
+ ];
56
+ if (params.modelName) {
57
+ lines.push(`Model: ${params.modelName}`);
58
+ }
59
+ lines.push(``, `You are now listed on the Kroxy job board. Incoming jobs will be sent as POST webhooks to your endpoint.`);
27
60
  const result = {
28
61
  registered: true,
29
62
  walletAddress: agent.walletAddress ?? wallet,
30
63
  name: agent.name ?? params.name,
31
64
  capabilities: agent.capabilities ?? [params.capability],
32
65
  pricingUsdc: agent.pricingUsdc ?? params.price,
66
+ modelName: params.modelName,
33
67
  endpoint: params.endpoint,
34
68
  };
35
69
  return {
36
- content: [{
37
- type: 'text',
38
- text: JSON.stringify(result, null, 2),
39
- }],
70
+ content: [{ type: 'text', text: lines.join('\n') }],
40
71
  details: result,
41
72
  };
42
73
  }
@@ -16,19 +16,27 @@ export async function executeReputation(params) {
16
16
  score >= 60 ? 'Good — established track record' :
17
17
  score >= 20 ? 'New — building reputation' :
18
18
  'Low — exercise caution';
19
+ const successCount = rep.successCount ?? 0;
20
+ const disputeCount = rep.disputeCount ?? 0;
21
+ const totalEarned = rep.totalEarned ?? '0';
22
+ const lines = [
23
+ `Reputation: ${walletAddress}`,
24
+ ``,
25
+ `Score: ${score}/100 — ${interpretation}`,
26
+ `Jobs won: ${successCount}`,
27
+ `Disputes: ${disputeCount}`,
28
+ `Total earned: $${totalEarned} USDC`,
29
+ ];
19
30
  const result = {
20
31
  address: walletAddress,
21
32
  score,
22
- successCount: rep.successCount ?? 0,
23
- disputeCount: rep.disputeCount ?? 0,
24
- totalEarned: rep.totalEarned ?? '0',
33
+ successCount,
34
+ disputeCount,
35
+ totalEarned,
25
36
  interpretation,
26
37
  };
27
38
  return {
28
- content: [{
29
- type: 'text',
30
- text: JSON.stringify(result, null, 2),
31
- }],
39
+ content: [{ type: 'text', text: lines.join('\n') }],
32
40
  details: result,
33
41
  };
34
42
  }
@@ -0,0 +1,10 @@
1
+ import { createHash } from 'node:crypto';
2
+ /**
3
+ * Deterministic demo wallet address derived from a fixed seed.
4
+ * Used in demo mode when no real wallet is configured.
5
+ */
6
+ export function demoDerivedAddress() {
7
+ const hash = createHash('sha256').update('kroxy-demo-wallet').digest('hex');
8
+ return `0x${hash.slice(0, 40)}`;
9
+ }
10
+ //# sourceMappingURL=wallet.js.map
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
- import { createHash } from 'node:crypto';
2
+ import { demoDerivedAddress } from './src/utils/wallet.js';
3
3
  import { hireParams, executeHire } from './src/tools/hire.js';
4
4
  import { offerParams, executeOffer } from './src/tools/offer.js';
5
5
  import { reputationParams, executeReputation } from './src/tools/reputation.js';
@@ -24,11 +24,6 @@ type AutoagentToolParams = Parameters<typeof executeAutoagent>[0];
24
24
 
25
25
  const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
26
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
-
32
27
  export default {
33
28
  id: 'kroxy',
34
29
  name: 'Kroxy',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kroxy/kroxy",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "type": "module",
5
5
  "description": "Trustless agent-to-agent payments via USDC escrow on Base blockchain",
6
6
  "license": "MIT",
@@ -1,19 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createHash } from 'node:crypto';
4
3
  import fs from 'node:fs';
5
4
  import os from 'node:os';
6
5
  import path from 'node:path';
7
6
  import process from 'node:process';
8
7
  import readline from 'node:readline/promises';
8
+ import { demoDerivedAddress } from '../dist/src/utils/wallet.js';
9
9
 
10
10
  const DEFAULT_API_URL = 'https://api-production-1b45.up.railway.app';
11
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
12
  function resolveConfigPath() {
18
13
  const explicit = process.env.OPENCLAW_CONFIG_PATH?.trim();
19
14
  if (explicit) return explicit;