@consensus-tools/consensus-tools 0.1.5 → 0.1.7

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.
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright [yyyy] [name of copyright owner]
189
+ Copyright 2026 consensus.tools
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
package/index.ts CHANGED
@@ -9,28 +9,35 @@ import { registerTools } from './src/tools';
9
9
  import { createService } from './src/service';
10
10
  import type { ConsensusToolsConfig, Job } from './src/types';
11
11
 
12
- export default async function register(api: any) {
12
+ export default function register(api: any) {
13
13
  const logger = api?.logger?.child ? api.logger.child({ plugin: PLUGIN_ID }) : api?.logger;
14
14
  const loaded = loadConfig(api, logger);
15
15
  const { config } = validateConfig(loaded, logger);
16
16
  const agentId = resolveAgentId(api, config);
17
17
 
18
18
  const storage = createStorage(config);
19
- await storage.init();
20
-
21
19
  const ledger = new LedgerEngine(storage, config, logger);
22
20
  const engine = new JobEngine(storage, ledger, config, logger);
23
21
  const server = new ConsensusToolsServer(config, engine, ledger, logger);
24
22
  const client = new ConsensusToolsClient(config.global.baseUrl, config.global.accessToken, logger);
25
23
 
26
- if (config.mode === 'local') {
27
- await ledger.applyConfigBalances(
28
- config.local.ledger.balances,
29
- config.local.ledger.balancesMode ?? 'initial'
30
- );
31
- }
24
+ let readyPromise: Promise<void> | null = null;
25
+ const ensureReady = async () => {
26
+ if (!readyPromise) {
27
+ readyPromise = (async () => {
28
+ await storage.init();
29
+ if (config.mode === 'local') {
30
+ await ledger.applyConfigBalances(
31
+ config.local.ledger.balances,
32
+ config.local.ledger.balancesMode ?? 'initial'
33
+ );
34
+ }
35
+ })();
36
+ }
37
+ return readyPromise;
38
+ };
32
39
 
33
- const backend = createBackend(config, engine, ledger, client, server, storage, logger, agentId);
40
+ const backend = createBackend(config, ensureReady, engine, ledger, client, server, storage, logger, agentId);
34
41
 
35
42
  api.registerCli(
36
43
  ({ program }: any) => {
@@ -49,11 +56,19 @@ export default async function register(api: any) {
49
56
  getJob: backend.getJob
50
57
  };
51
58
  const service = createService(config, serviceBackend, agentId, capabilities, logger);
52
- api.registerService({ id: 'consensus-tools', start: service.start, stop: service.stop });
59
+ api.registerService({
60
+ id: 'consensus-tools',
61
+ start: async () => {
62
+ await ensureReady();
63
+ await service.start();
64
+ },
65
+ stop: service.stop
66
+ });
53
67
  }
54
68
 
55
69
  function createBackend(
56
70
  config: ConsensusToolsConfig,
71
+ ensureReady: () => Promise<void>,
57
72
  engine: JobEngine,
58
73
  ledger: LedgerEngine,
59
74
  client: ConsensusToolsClient,
@@ -90,6 +105,7 @@ function createBackend(
90
105
 
91
106
  const backend = {
92
107
  postJob: async (actorId: string, input: any): Promise<Job> => {
108
+ await ensureReady();
93
109
  if (config.mode === 'global') {
94
110
  ensureGlobalAccess('post jobs');
95
111
  ensureNetworkSideEffects('post jobs');
@@ -103,6 +119,7 @@ function createBackend(
103
119
  }
104
120
  },
105
121
  listJobs: async (filters?: Record<string, string | undefined>): Promise<Job[]> => {
122
+ await ensureReady();
106
123
  if (config.mode === 'global') {
107
124
  ensureGlobalAccess('list jobs');
108
125
  return client.listJobs(filters || {});
@@ -110,6 +127,7 @@ function createBackend(
110
127
  return engine.listJobs(filters || {});
111
128
  },
112
129
  getJob: async (jobId: string): Promise<Job | undefined> => {
130
+ await ensureReady();
113
131
  if (config.mode === 'global') {
114
132
  ensureGlobalAccess('get jobs');
115
133
  return client.getJob(jobId);
@@ -117,6 +135,7 @@ function createBackend(
117
135
  return engine.getJob(jobId);
118
136
  },
119
137
  getStatus: async (jobId: string): Promise<any> => {
138
+ await ensureReady();
120
139
  if (config.mode === 'global') {
121
140
  ensureGlobalAccess('get job status');
122
141
  return client.getStatus(jobId);
@@ -124,6 +143,7 @@ function createBackend(
124
143
  return engine.getStatus(jobId);
125
144
  },
126
145
  claimJob: async (actorId: string, jobId: string, stakeAmount: number, leaseSeconds: number): Promise<any> => {
146
+ await ensureReady();
127
147
  if (config.mode === 'global') {
128
148
  ensureGlobalAccess('claim jobs');
129
149
  ensureNetworkSideEffects('claim jobs');
@@ -137,12 +157,14 @@ function createBackend(
137
157
  }
138
158
  },
139
159
  heartbeat: async (actorId: string, jobId: string): Promise<void> => {
160
+ await ensureReady();
140
161
  if (config.mode === 'global') {
141
162
  return;
142
163
  }
143
164
  return engine.heartbeat(actorId, jobId);
144
165
  },
145
166
  submitJob: async (actorId: string, jobId: string, input: any): Promise<any> => {
167
+ await ensureReady();
146
168
  if (config.mode === 'global') {
147
169
  ensureGlobalAccess('submit jobs');
148
170
  ensureNetworkSideEffects('submit jobs');
@@ -156,6 +178,7 @@ function createBackend(
156
178
  }
157
179
  },
158
180
  listSubmissions: async (jobId: string): Promise<any[]> => {
181
+ await ensureReady();
159
182
  if (config.mode === 'global') {
160
183
  ensureGlobalAccess('list submissions');
161
184
  const status = await client.getStatus(jobId);
@@ -165,6 +188,7 @@ function createBackend(
165
188
  return status.submissions;
166
189
  },
167
190
  listVotes: async (jobId: string): Promise<any[]> => {
191
+ await ensureReady();
168
192
  if (config.mode === 'global') {
169
193
  ensureGlobalAccess('list votes');
170
194
  const status = await client.getStatus(jobId);
@@ -174,6 +198,7 @@ function createBackend(
174
198
  return status.votes;
175
199
  },
176
200
  vote: async (actorId: string, jobId: string, input: any): Promise<any> => {
201
+ await ensureReady();
177
202
  if (config.mode === 'global') {
178
203
  ensureGlobalAccess('vote');
179
204
  ensureNetworkSideEffects('vote');
@@ -187,6 +212,7 @@ function createBackend(
187
212
  }
188
213
  },
189
214
  resolveJob: async (actorId: string, jobId: string, input: any): Promise<any> => {
215
+ await ensureReady();
190
216
  if (config.mode === 'global') {
191
217
  ensureGlobalAccess('resolve jobs');
192
218
  ensureNetworkSideEffects('resolve jobs');
@@ -200,6 +226,7 @@ function createBackend(
200
226
  }
201
227
  },
202
228
  getLedgerBalance: async (target: string): Promise<number> => {
229
+ await ensureReady();
203
230
  if (config.mode === 'global') {
204
231
  ensureGlobalAccess('read ledger');
205
232
  const result = await client.getLedger(target);
@@ -208,6 +235,7 @@ function createBackend(
208
235
  return ledger.getBalance(target);
209
236
  },
210
237
  faucet: async (target: string, amount: number): Promise<any> => {
238
+ await ensureReady();
211
239
  if (!config.local.ledger.faucetEnabled) {
212
240
  throw new Error('Faucet disabled');
213
241
  }
@@ -219,6 +247,7 @@ function createBackend(
219
247
  return ledger.faucet(target, amount, `faucet:${agentId}`);
220
248
  },
221
249
  getDiagnostics: async () => {
250
+ await ensureReady();
222
251
  const errors = (await storage.getState()).errors.map((err: any) => ({ at: err.at, message: err.message }));
223
252
  let networkOk: boolean | undefined = undefined;
224
253
  if (config.mode === 'global') {
@@ -229,13 +258,16 @@ function createBackend(
229
258
  await client.listJobs({});
230
259
  networkOk = true;
231
260
  } catch (err) {
232
- logger?.warn?.({ err }, 'consensus-tools: diagnostics network check failed');
261
+ logger?.warn?.(`consensus-tools: diagnostics network check failed: ${err instanceof Error ? err.message : String(err)}`);
233
262
  networkOk = false;
234
263
  }
235
264
  }
236
265
  return { errors, networkOk };
237
266
  },
238
- startServer: async () => server.start(),
267
+ startServer: async () => {
268
+ await ensureReady();
269
+ return server.start();
270
+ },
239
271
  stopServer: async () => server.stop()
240
272
  };
241
273
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "consensus-tools",
3
3
  "name": "consensus-tools",
4
- "version": "0.1.1",
4
+ "version": "0.1.4",
5
5
  "main": "index.ts",
6
6
  "description": "consensus-tools distributed job board for OpenClaw agents",
7
7
  "configSchema": {
@@ -10,7 +10,10 @@
10
10
  "properties": {
11
11
  "mode": {
12
12
  "type": "string",
13
- "enum": ["local", "global"],
13
+ "enum": [
14
+ "local",
15
+ "global"
16
+ ],
14
17
  "default": "local",
15
18
  "description": "Choose local storage or a hosted global job board"
16
19
  },
@@ -24,7 +27,10 @@
24
27
  "properties": {
25
28
  "kind": {
26
29
  "type": "string",
27
- "enum": ["sqlite", "json"],
30
+ "enum": [
31
+ "sqlite",
32
+ "json"
33
+ ],
28
34
  "default": "json"
29
35
  },
30
36
  "path": {
@@ -32,51 +38,199 @@
32
38
  "default": "./.openclaw/consensus-tools.json"
33
39
  }
34
40
  },
35
- "required": ["kind", "path"]
41
+ "required": [
42
+ "kind",
43
+ "path"
44
+ ]
36
45
  },
37
46
  "server": {
38
47
  "type": "object",
39
48
  "additionalProperties": false,
40
49
  "properties": {
41
- "enabled": { "type": "boolean", "default": false },
42
- "host": { "type": "string", "default": "127.0.0.1" },
43
- "port": { "type": "integer", "default": 9888, "minimum": 1, "maximum": 65535 },
44
- "authToken": { "type": "string", "default": "" }
50
+ "enabled": {
51
+ "type": "boolean",
52
+ "default": false
53
+ },
54
+ "host": {
55
+ "type": "string",
56
+ "default": "127.0.0.1"
57
+ },
58
+ "port": {
59
+ "type": "integer",
60
+ "default": 9888,
61
+ "minimum": 1,
62
+ "maximum": 65535
63
+ },
64
+ "authToken": {
65
+ "type": "string",
66
+ "default": ""
67
+ }
45
68
  },
46
- "required": ["enabled", "host", "port", "authToken"]
69
+ "required": [
70
+ "enabled",
71
+ "host",
72
+ "port",
73
+ "authToken"
74
+ ]
75
+ },
76
+ "slashingEnabled": {
77
+ "type": "boolean",
78
+ "default": false
47
79
  },
48
- "slashingEnabled": { "type": "boolean", "default": false },
49
80
  "jobDefaults": {
50
81
  "type": "object",
51
82
  "additionalProperties": false,
52
83
  "properties": {
53
- "reward": { "type": "number", "default": 10, "minimum": 0 },
54
- "stakeRequired": { "type": "number", "default": 1, "minimum": 0 },
55
- "maxParticipants": { "type": "integer", "default": 3, "minimum": 1 },
56
- "minParticipants": { "type": "integer", "default": 1, "minimum": 1 },
57
- "expiresSeconds": { "type": "integer", "default": 86400, "minimum": 60 },
84
+ "reward": {
85
+ "type": "number",
86
+ "default": 10,
87
+ "minimum": 0
88
+ },
89
+ "stakeRequired": {
90
+ "type": "number",
91
+ "default": 1,
92
+ "minimum": 0
93
+ },
94
+ "maxParticipants": {
95
+ "type": "integer",
96
+ "default": 3,
97
+ "minimum": 1
98
+ },
99
+ "minParticipants": {
100
+ "type": "integer",
101
+ "default": 1,
102
+ "minimum": 1
103
+ },
104
+ "expiresSeconds": {
105
+ "type": "integer",
106
+ "default": 86400,
107
+ "minimum": 60
108
+ },
58
109
  "consensusPolicy": {
59
110
  "type": "object",
60
111
  "additionalProperties": false,
61
112
  "properties": {
62
113
  "type": {
63
114
  "type": "string",
64
- "enum": ["FIRST_SUBMISSION_WINS", "APPROVAL_VOTE", "MAJORITY_VOTE", "WEIGHTED_REPUTATION", "TRUSTED_ARBITER"],
115
+ "enum": [
116
+ "FIRST_SUBMISSION_WINS",
117
+ "SINGLE_WINNER",
118
+ "HIGHEST_CONFIDENCE_SINGLE",
119
+ "APPROVAL_VOTE",
120
+ "OWNER_PICK",
121
+ "TOP_K_SPLIT",
122
+ "MAJORITY_VOTE",
123
+ "WEIGHTED_VOTE_SIMPLE",
124
+ "WEIGHTED_REPUTATION",
125
+ "TRUSTED_ARBITER"
126
+ ],
65
127
  "default": "FIRST_SUBMISSION_WINS"
66
128
  },
67
- "trustedArbiterAgentId": { "type": "string", "default": "" }
129
+ "trustedArbiterAgentId": {
130
+ "type": "string",
131
+ "default": ""
132
+ },
133
+ "minConfidence": {
134
+ "type": "number",
135
+ "default": 0,
136
+ "minimum": 0,
137
+ "maximum": 1
138
+ },
139
+ "topK": {
140
+ "type": "integer",
141
+ "default": 2,
142
+ "minimum": 1
143
+ },
144
+ "ordering": {
145
+ "type": "string",
146
+ "enum": [
147
+ "confidence",
148
+ "score"
149
+ ],
150
+ "default": "confidence"
151
+ },
152
+ "quorum": {
153
+ "type": "integer",
154
+ "minimum": 1
155
+ },
156
+ "minScore": {
157
+ "type": "number"
158
+ },
159
+ "minMargin": {
160
+ "type": "number"
161
+ },
162
+ "tieBreak": {
163
+ "type": "string",
164
+ "enum": [
165
+ "earliest",
166
+ "confidence",
167
+ "arbiter"
168
+ ]
169
+ },
170
+ "approvalVote": {
171
+ "type": "object",
172
+ "additionalProperties": false,
173
+ "properties": {
174
+ "weightMode": {
175
+ "type": "string",
176
+ "enum": [
177
+ "equal",
178
+ "explicit",
179
+ "reputation"
180
+ ]
181
+ },
182
+ "settlement": {
183
+ "type": "string",
184
+ "enum": [
185
+ "immediate",
186
+ "staked",
187
+ "oracle"
188
+ ]
189
+ },
190
+ "oracle": {
191
+ "type": "string",
192
+ "enum": [
193
+ "trusted_arbiter"
194
+ ]
195
+ },
196
+ "voteSlashPercent": {
197
+ "type": "number",
198
+ "minimum": 0,
199
+ "maximum": 1
200
+ }
201
+ }
202
+ }
68
203
  },
69
- "required": ["type", "trustedArbiterAgentId"]
204
+ "required": [
205
+ "type",
206
+ "trustedArbiterAgentId"
207
+ ]
70
208
  },
71
209
  "slashingPolicy": {
72
210
  "type": "object",
73
211
  "additionalProperties": false,
74
212
  "properties": {
75
- "enabled": { "type": "boolean", "default": false },
76
- "slashPercent": { "type": "number", "default": 0, "minimum": 0, "maximum": 1 },
77
- "slashFlat": { "type": "number", "default": 0, "minimum": 0 }
213
+ "enabled": {
214
+ "type": "boolean",
215
+ "default": false
216
+ },
217
+ "slashPercent": {
218
+ "type": "number",
219
+ "default": 0,
220
+ "minimum": 0,
221
+ "maximum": 1
222
+ },
223
+ "slashFlat": {
224
+ "type": "number",
225
+ "default": 0,
226
+ "minimum": 0
227
+ }
78
228
  },
79
- "required": ["enabled", "slashPercent", "slashFlat"]
229
+ "required": [
230
+ "enabled",
231
+ "slashPercent",
232
+ "slashFlat"
233
+ ]
80
234
  }
81
235
  },
82
236
  "required": [
@@ -93,19 +247,145 @@
93
247
  "type": "object",
94
248
  "additionalProperties": false,
95
249
  "properties": {
96
- "faucetEnabled": { "type": "boolean", "default": false },
97
- "initialCreditsPerAgent": { "type": "number", "default": 0, "minimum": 0 },
98
- "balancesMode": { "type": "string", "enum": ["initial", "override"], "default": "initial" },
250
+ "faucetEnabled": {
251
+ "type": "boolean",
252
+ "default": false
253
+ },
254
+ "initialCreditsPerAgent": {
255
+ "type": "number",
256
+ "default": 0,
257
+ "minimum": 0
258
+ },
259
+ "balancesMode": {
260
+ "type": "string",
261
+ "enum": [
262
+ "initial",
263
+ "override"
264
+ ],
265
+ "default": "initial"
266
+ },
99
267
  "balances": {
100
268
  "type": "object",
101
- "additionalProperties": { "type": "number", "minimum": 0 },
269
+ "additionalProperties": {
270
+ "type": "number",
271
+ "minimum": 0
272
+ },
102
273
  "default": {}
103
274
  }
104
275
  },
105
- "required": ["faucetEnabled", "initialCreditsPerAgent", "balancesMode", "balances"]
276
+ "required": [
277
+ "faucetEnabled",
278
+ "initialCreditsPerAgent",
279
+ "balancesMode",
280
+ "balances"
281
+ ]
282
+ },
283
+ "consensusPolicies": {
284
+ "type": "object",
285
+ "additionalProperties": {
286
+ "type": "object",
287
+ "additionalProperties": false,
288
+ "properties": {
289
+ "type": {
290
+ "type": "string",
291
+ "enum": [
292
+ "FIRST_SUBMISSION_WINS",
293
+ "SINGLE_WINNER",
294
+ "HIGHEST_CONFIDENCE_SINGLE",
295
+ "APPROVAL_VOTE",
296
+ "OWNER_PICK",
297
+ "TOP_K_SPLIT",
298
+ "MAJORITY_VOTE",
299
+ "WEIGHTED_VOTE_SIMPLE",
300
+ "WEIGHTED_REPUTATION",
301
+ "TRUSTED_ARBITER"
302
+ ]
303
+ },
304
+ "trustedArbiterAgentId": {
305
+ "type": "string"
306
+ },
307
+ "minConfidence": {
308
+ "type": "number",
309
+ "minimum": 0,
310
+ "maximum": 1
311
+ },
312
+ "topK": {
313
+ "type": "integer",
314
+ "minimum": 1
315
+ },
316
+ "ordering": {
317
+ "type": "string",
318
+ "enum": [
319
+ "confidence",
320
+ "score"
321
+ ]
322
+ },
323
+ "quorum": {
324
+ "type": "integer",
325
+ "minimum": 1
326
+ },
327
+ "minScore": {
328
+ "type": "number"
329
+ },
330
+ "minMargin": {
331
+ "type": "number"
332
+ },
333
+ "tieBreak": {
334
+ "type": "string",
335
+ "enum": [
336
+ "earliest",
337
+ "confidence",
338
+ "arbiter"
339
+ ]
340
+ },
341
+ "approvalVote": {
342
+ "type": "object",
343
+ "additionalProperties": false,
344
+ "properties": {
345
+ "weightMode": {
346
+ "type": "string",
347
+ "enum": [
348
+ "equal",
349
+ "explicit",
350
+ "reputation"
351
+ ]
352
+ },
353
+ "settlement": {
354
+ "type": "string",
355
+ "enum": [
356
+ "immediate",
357
+ "staked",
358
+ "oracle"
359
+ ]
360
+ },
361
+ "oracle": {
362
+ "type": "string",
363
+ "enum": [
364
+ "trusted_arbiter"
365
+ ]
366
+ },
367
+ "voteSlashPercent": {
368
+ "type": "number",
369
+ "minimum": 0,
370
+ "maximum": 1
371
+ }
372
+ }
373
+ }
374
+ },
375
+ "required": [
376
+ "type"
377
+ ]
378
+ },
379
+ "default": {}
106
380
  }
107
381
  },
108
- "required": ["storage", "server", "slashingEnabled", "jobDefaults", "ledger"]
382
+ "required": [
383
+ "storage",
384
+ "server",
385
+ "slashingEnabled",
386
+ "jobDefaults",
387
+ "ledger"
388
+ ]
109
389
  },
110
390
  "global": {
111
391
  "type": "object",
@@ -120,7 +400,10 @@
120
400
  "default": ""
121
401
  }
122
402
  },
123
- "required": ["baseUrl", "accessToken"]
403
+ "required": [
404
+ "baseUrl",
405
+ "accessToken"
406
+ ]
124
407
  },
125
408
  "agentIdentity": {
126
409
  "type": "object",
@@ -128,7 +411,11 @@
128
411
  "properties": {
129
412
  "agentIdSource": {
130
413
  "type": "string",
131
- "enum": ["openclaw", "env", "manual"],
414
+ "enum": [
415
+ "openclaw",
416
+ "env",
417
+ "manual"
418
+ ],
132
419
  "default": "openclaw"
133
420
  },
134
421
  "manualAgentId": {
@@ -136,50 +423,108 @@
136
423
  "default": ""
137
424
  }
138
425
  },
139
- "required": ["agentIdSource", "manualAgentId"]
426
+ "required": [
427
+ "agentIdSource",
428
+ "manualAgentId"
429
+ ]
140
430
  },
141
431
  "safety": {
142
432
  "type": "object",
143
433
  "additionalProperties": false,
144
434
  "properties": {
145
- "requireOptionalToolsOptIn": { "type": "boolean", "default": true },
146
- "allowNetworkSideEffects": { "type": "boolean", "default": false }
435
+ "requireOptionalToolsOptIn": {
436
+ "type": "boolean",
437
+ "default": true
438
+ },
439
+ "allowNetworkSideEffects": {
440
+ "type": "boolean",
441
+ "default": false
442
+ }
147
443
  },
148
- "required": ["requireOptionalToolsOptIn", "allowNetworkSideEffects"]
444
+ "required": [
445
+ "requireOptionalToolsOptIn",
446
+ "allowNetworkSideEffects"
447
+ ]
149
448
  }
150
449
  },
151
- "required": ["mode", "agentIdentity", "safety"],
450
+ "required": [
451
+ "mode",
452
+ "agentIdentity",
453
+ "safety"
454
+ ],
152
455
  "allOf": [
153
456
  {
154
- "if": { "properties": { "mode": { "const": "local" } } },
155
- "then": { "required": ["local"] }
457
+ "if": {
458
+ "properties": {
459
+ "mode": {
460
+ "const": "local"
461
+ }
462
+ }
463
+ },
464
+ "then": {
465
+ "required": [
466
+ "local"
467
+ ]
468
+ }
156
469
  },
157
470
  {
158
- "if": { "properties": { "mode": { "const": "global" } } },
159
- "then": { "required": ["global"] }
471
+ "if": {
472
+ "properties": {
473
+ "mode": {
474
+ "const": "global"
475
+ }
476
+ }
477
+ },
478
+ "then": {
479
+ "required": [
480
+ "global"
481
+ ]
482
+ }
160
483
  }
161
484
  ]
162
485
  },
163
486
  "uiHints": {
164
487
  "local": {
165
488
  "storage": {
166
- "path": { "ui:widget": "file" }
489
+ "path": {
490
+ "ui:widget": "file"
491
+ }
167
492
  },
168
493
  "server": {
169
- "authToken": { "ui:widget": "password", "sensitive": true }
494
+ "authToken": {
495
+ "ui:widget": "password",
496
+ "sensitive": true
497
+ }
170
498
  },
171
499
  "ledger": {
172
- "faucetEnabled": { "ui:widget": "checkbox", "warning": "Dev-only faucet" },
173
- "balancesMode": { "label": "Balance mode", "ui:widget": "radio" },
174
- "balances": { "label": "Per-agent balances" }
500
+ "faucetEnabled": {
501
+ "ui:widget": "checkbox",
502
+ "warning": "Dev-only faucet"
503
+ },
504
+ "balancesMode": {
505
+ "label": "Balance mode",
506
+ "ui:widget": "radio"
507
+ },
508
+ "balances": {
509
+ "label": "Per-agent balances"
510
+ }
175
511
  }
176
512
  },
177
513
  "global": {
178
- "baseUrl": { "label": "Global board URL" },
179
- "accessToken": { "ui:widget": "password", "sensitive": true, "label": "Access Token" }
514
+ "baseUrl": {
515
+ "label": "Global board URL"
516
+ },
517
+ "accessToken": {
518
+ "ui:widget": "password",
519
+ "sensitive": true,
520
+ "label": "Access Token"
521
+ }
180
522
  },
181
523
  "safety": {
182
- "allowNetworkSideEffects": { "ui:widget": "checkbox", "warning": "Enables network mutations" }
524
+ "allowNetworkSideEffects": {
525
+ "ui:widget": "checkbox",
526
+ "warning": "Enables network mutations"
527
+ }
183
528
  }
184
529
  }
185
530
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consensus-tools/consensus-tools",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "bin": {
package/src/config.ts CHANGED
@@ -19,8 +19,7 @@ export const configSchema = {
19
19
  properties: {
20
20
  kind: { type: 'string', enum: ['sqlite', 'json'], default: 'json' },
21
21
  path: { type: 'string', default: './.openclaw/consensus-tools.json' }
22
- },
23
- required: ['kind', 'path']
22
+ }
24
23
  },
25
24
  server: {
26
25
  type: 'object',
@@ -31,7 +30,6 @@ export const configSchema = {
31
30
  port: { type: 'integer', default: 9888, minimum: 1, maximum: 65535 },
32
31
  authToken: { type: 'string', default: '' }
33
32
  },
34
- required: ['enabled', 'host', 'port', 'authToken']
35
33
  },
36
34
  slashingEnabled: { type: 'boolean', default: false },
37
35
  jobDefaults: {
@@ -51,6 +49,7 @@ export const configSchema = {
51
49
  type: 'string',
52
50
  enum: [
53
51
  'FIRST_SUBMISSION_WINS',
52
+ 'SINGLE_WINNER',
54
53
  'HIGHEST_CONFIDENCE_SINGLE',
55
54
  'APPROVAL_VOTE',
56
55
  'OWNER_PICK',
@@ -66,9 +65,21 @@ export const configSchema = {
66
65
  minConfidence: { type: 'number', default: 0, minimum: 0, maximum: 1 },
67
66
  topK: { type: 'integer', default: 2, minimum: 1 },
68
67
  ordering: { type: 'string', enum: ['confidence', 'score'], default: 'confidence' },
69
- quorum: { type: 'integer', minimum: 1 }
68
+ quorum: { type: 'integer', minimum: 1 },
69
+ minScore: { type: 'number' },
70
+ minMargin: { type: 'number' },
71
+ tieBreak: { type: 'string', enum: ['earliest', 'confidence', 'arbiter'] },
72
+ approvalVote: {
73
+ type: 'object',
74
+ additionalProperties: false,
75
+ properties: {
76
+ weightMode: { type: 'string', enum: ['equal', 'explicit', 'reputation'] },
77
+ settlement: { type: 'string', enum: ['immediate', 'staked', 'oracle'] },
78
+ oracle: { type: 'string', enum: ['trusted_arbiter'] },
79
+ voteSlashPercent: { type: 'number', minimum: 0, maximum: 1 }
80
+ }
81
+ }
70
82
  },
71
- required: ['type', 'trustedArbiterAgentId']
72
83
  },
73
84
  slashingPolicy: {
74
85
  type: 'object',
@@ -78,18 +89,8 @@ export const configSchema = {
78
89
  slashPercent: { type: 'number', default: 0, minimum: 0, maximum: 1 },
79
90
  slashFlat: { type: 'number', default: 0, minimum: 0 }
80
91
  },
81
- required: ['enabled', 'slashPercent', 'slashFlat']
82
92
  }
83
93
  },
84
- required: [
85
- 'reward',
86
- 'stakeRequired',
87
- 'maxParticipants',
88
- 'minParticipants',
89
- 'expiresSeconds',
90
- 'consensusPolicy',
91
- 'slashingPolicy'
92
- ]
93
94
  },
94
95
  consensusPolicies: {
95
96
  type: 'object',
@@ -101,6 +102,7 @@ export const configSchema = {
101
102
  type: 'string',
102
103
  enum: [
103
104
  'FIRST_SUBMISSION_WINS',
105
+ 'SINGLE_WINNER',
104
106
  'HIGHEST_CONFIDENCE_SINGLE',
105
107
  'APPROVAL_VOTE',
106
108
  'OWNER_PICK',
@@ -115,9 +117,21 @@ export const configSchema = {
115
117
  minConfidence: { type: 'number', minimum: 0, maximum: 1 },
116
118
  topK: { type: 'integer', minimum: 1 },
117
119
  ordering: { type: 'string', enum: ['confidence', 'score'] },
118
- quorum: { type: 'integer', minimum: 1 }
120
+ quorum: { type: 'integer', minimum: 1 },
121
+ minScore: { type: 'number' },
122
+ minMargin: { type: 'number' },
123
+ tieBreak: { type: 'string', enum: ['earliest', 'confidence', 'arbiter'] },
124
+ approvalVote: {
125
+ type: 'object',
126
+ additionalProperties: false,
127
+ properties: {
128
+ weightMode: { type: 'string', enum: ['equal', 'explicit', 'reputation'] },
129
+ settlement: { type: 'string', enum: ['immediate', 'staked', 'oracle'] },
130
+ oracle: { type: 'string', enum: ['trusted_arbiter'] },
131
+ voteSlashPercent: { type: 'number', minimum: 0, maximum: 1 }
132
+ }
133
+ }
119
134
  },
120
- required: ['type']
121
135
  },
122
136
  default: {}
123
137
  },
@@ -134,10 +148,8 @@ export const configSchema = {
134
148
  default: {}
135
149
  }
136
150
  },
137
- required: ['faucetEnabled', 'initialCreditsPerAgent', 'balancesMode', 'balances']
138
151
  }
139
152
  },
140
- required: ['storage', 'server', 'slashingEnabled', 'jobDefaults', 'ledger']
141
153
  },
142
154
  global: {
143
155
  type: 'object',
@@ -146,7 +158,6 @@ export const configSchema = {
146
158
  baseUrl: { type: 'string', default: 'http://localhost:9888' },
147
159
  accessToken: { type: 'string', default: '' }
148
160
  },
149
- required: ['baseUrl', 'accessToken']
150
161
  },
151
162
  agentIdentity: {
152
163
  type: 'object',
@@ -155,7 +166,6 @@ export const configSchema = {
155
166
  agentIdSource: { type: 'string', enum: ['openclaw', 'env', 'manual'], default: 'openclaw' },
156
167
  manualAgentId: { type: 'string', default: '' }
157
168
  },
158
- required: ['agentIdSource', 'manualAgentId']
159
169
  },
160
170
  safety: {
161
171
  type: 'object',
@@ -164,20 +174,8 @@ export const configSchema = {
164
174
  requireOptionalToolsOptIn: { type: 'boolean', default: true },
165
175
  allowNetworkSideEffects: { type: 'boolean', default: false }
166
176
  },
167
- required: ['requireOptionalToolsOptIn', 'allowNetworkSideEffects']
168
177
  }
169
178
  },
170
- required: ['mode', 'agentIdentity', 'safety'],
171
- allOf: [
172
- {
173
- if: { properties: { mode: { const: 'local' } } },
174
- then: { required: ['local'] }
175
- },
176
- {
177
- if: { properties: { mode: { const: 'global' } } },
178
- then: { required: ['global'] }
179
- }
180
- ]
181
179
  } as const;
182
180
 
183
181
  export const defaultConfig: ConsensusToolsConfig = {
@@ -234,14 +232,14 @@ export function loadConfig(api: any, logger?: any): ConsensusToolsConfig {
234
232
  try {
235
233
  raw = api?.config?.getPluginConfig?.(PLUGIN_ID);
236
234
  } catch (err) {
237
- logger?.warn?.({ err }, 'consensus-tools: failed to read config via getPluginConfig');
235
+ logger?.warn?.(`consensus-tools: failed to read config via getPluginConfig: ${err instanceof Error ? err.message : String(err)}`);
238
236
  }
239
237
 
240
238
  if (!raw) {
241
239
  try {
242
240
  raw = api?.config?.get?.(`plugins.entries.${PLUGIN_ID}.config`);
243
241
  } catch (err) {
244
- logger?.warn?.({ err }, 'consensus-tools: failed to read config via config.get');
242
+ logger?.warn?.(`consensus-tools: failed to read config via config.get: ${err instanceof Error ? err.message : String(err)}`);
245
243
  }
246
244
  }
247
245
 
@@ -253,7 +251,30 @@ export function loadConfig(api: any, logger?: any): ConsensusToolsConfig {
253
251
  raw = api?.config?.entries?.[PLUGIN_ID]?.config;
254
252
  }
255
253
 
256
- return mergeDefaults(fallback, raw ?? {});
254
+ return normalizeLegacyPolicyAliases(mergeDefaults(fallback, raw ?? {}));
255
+ }
256
+
257
+ function normalizeLegacyPolicyAliases(config: ConsensusToolsConfig): ConsensusToolsConfig {
258
+ const normalized = deepCopy(config);
259
+
260
+ const normalizePolicyType = (value?: string) => {
261
+ if (value === 'SINGLE_WINNER') return 'FIRST_SUBMISSION_WINS';
262
+ return value;
263
+ };
264
+
265
+ const defaultsPolicy = normalized?.local?.jobDefaults?.consensusPolicy;
266
+ if (defaultsPolicy?.type) {
267
+ defaultsPolicy.type = normalizePolicyType(defaultsPolicy.type) as any;
268
+ }
269
+
270
+ const policyMap = normalized?.local?.consensusPolicies ?? {};
271
+ for (const policy of Object.values(policyMap)) {
272
+ if (policy?.type) {
273
+ policy.type = normalizePolicyType(policy.type) as any;
274
+ }
275
+ }
276
+
277
+ return normalized;
257
278
  }
258
279
 
259
280
  export function validateConfig(input: ConsensusToolsConfig, logger?: any): { config: ConsensusToolsConfig; errors: string[] } {
@@ -264,7 +285,7 @@ export function validateConfig(input: ConsensusToolsConfig, logger?: any): { con
264
285
  : (validate.errors || []).map((err) => `${err.instancePath || '/'} ${err.message || 'invalid'}`);
265
286
 
266
287
  if (!ok) {
267
- logger?.warn?.({ errors }, 'consensus-tools: config validation warnings');
288
+ logger?.warn?.(`consensus-tools: config validation warnings: ${JSON.stringify(errors)}`);
268
289
  }
269
290
 
270
291
  return { config: candidate, errors };
@@ -149,7 +149,7 @@ export class JobEngine {
149
149
  });
150
150
  });
151
151
 
152
- this.logger?.info?.({ jobId: job.id }, 'consensus-tools: job posted');
152
+ this.logger?.info?.(`consensus-tools: job posted (${job.id})`);
153
153
  return job;
154
154
  }
155
155
 
@@ -127,7 +127,7 @@ export class LedgerEngine {
127
127
  ensureNonNegative(nextBalance, `${entry.agentId} after ${entry.type}`);
128
128
  state.ledger.push(entry);
129
129
  });
130
- this.logger?.info?.({ entry }, 'consensus-tools: ledger entry');
130
+ this.logger?.info?.(`consensus-tools: ledger entry (${entry.type} ${entry.amount} agent=${entry.agentId}${entry.jobId ? ` job=${entry.jobId}` : ''})`);
131
131
  if (this.config.local.ledger.balancesMode === 'override') {
132
132
  await this.applyConfigBalances(this.config.local.ledger.balances, 'override');
133
133
  }
@@ -59,7 +59,7 @@ export class ConsensusToolsClient {
59
59
 
60
60
  if (!res.ok) {
61
61
  const text = await res.text();
62
- this.logger?.warn?.({ status: res.status, path }, 'consensus-tools: network request failed');
62
+ this.logger?.warn?.(`consensus-tools: network request failed (status=${res.status}, path=${path})`);
63
63
  throw new Error(`Network error ${res.status}: ${text || res.statusText}`);
64
64
  }
65
65
 
@@ -22,7 +22,7 @@ export class ConsensusToolsServer {
22
22
  this.server?.listen(port, host, () => resolve());
23
23
  });
24
24
 
25
- this.logger?.info?.({ host, port }, 'consensus-tools embedded server started');
25
+ this.logger?.info?.(`consensus-tools embedded server started (host=${host}, port=${port})`);
26
26
  }
27
27
 
28
28
  async stop(): Promise<void> {
@@ -113,7 +113,7 @@ export class ConsensusToolsServer {
113
113
 
114
114
  return this.reply(res, 404, { error: 'Not found' });
115
115
  } catch (err: any) {
116
- this.logger?.warn?.({ err }, 'consensus-tools server error');
116
+ this.logger?.warn?.(`consensus-tools server error: ${err instanceof Error ? err.message : String(err)}`);
117
117
  try {
118
118
  await this.engine.recordError?.(err?.message || 'Server error', { path: req.url, method: req.method });
119
119
  } catch {
package/src/service.ts CHANGED
@@ -16,7 +16,7 @@ export function createService(
16
16
  id: 'consensus-tools-service',
17
17
  start: async () => {
18
18
  if (config.mode === 'global') return;
19
- logger?.debug?.({ agentId, capabilities }, 'consensus-tools: service started');
19
+ logger?.debug?.(`consensus-tools: service started (agentId=${agentId}, capabilities=${Array.isArray(capabilities) ? capabilities.join(',') : ''})`);
20
20
  },
21
21
  stop: async () => {
22
22
  if (config.mode === 'global') return;